浏览代码

Support use expr cond as update sets

1.Support use expr cond as update sets
2.feat: SQL injection harmless disposal
3.add CondIf interface
Unknown 6 年之前
父节点
当前提交
0ee351fedc
共有 12 个文件被更改,包括 152 次插入36 次删除
  1. 12 15
      README.md
  2. 5 5
      builder.go
  3. 9 2
      builder_update.go
  4. 5 0
      builder_update_test.go
  5. 3 2
      cond_eq.go
  6. 4 0
      cond_expr.go
  7. 49 0
      cond_if.go
  8. 38 0
      cond_if_test.go
  9. 11 11
      doc.go
  10. 5 0
      go.mod
  11. 5 1
      sql.go
  12. 6 0
      sql_test.go

+ 12 - 15
README.md

@@ -1,13 +1,10 @@
 # SQL builder
 
-[![GitCI.cn](https://gitci.cn/api/badges/go-xorm/builder/status.svg)](https://gitci.cn/go-xorm/builder)  [![codecov](https://codecov.io/gh/go-xorm/builder/branch/master/graph/badge.svg)](https://codecov.io/gh/go-xorm/builder)
-[![](https://goreportcard.com/badge/github.com/go-xorm/builder)](https://goreportcard.com/report/github.com/go-xorm/builder)
-
 Package builder is a lightweight and fast SQL builder for Go and XORM.
 
 Make sure you have installed Go 1.8+ and then:
 
-    go get github.com/go-xorm/builder
+    go get github.com/xormplus/builder
 
 # Insert
 
@@ -71,7 +68,7 @@ sql, args, err := Select("*").From("a").Where(Eq{"status": "1"}).
 * `Eq` is a redefine of a map, you can give one or more conditions to `Eq`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Eq{"a":1})
 // a=? [1]
@@ -90,7 +87,7 @@ sql, args, _ := ToSQL(Eq{"b": 1, "c":[]int{2, 3}})
 * `Neq` is the same to `Eq`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Neq{"a":1})
 // a<>? [1]
@@ -109,7 +106,7 @@ sql, args, _ := ToSQL(Neq{"b": 1, "c":[]int{2, 3}})
 * `Gt`, `Gte`, `Lt`, `Lte`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
 // a>? AND b>=? [1, 2]
@@ -120,7 +117,7 @@ sql, args, _ := ToSQL(Lt{"a", 1}.Or(Lte{"b", 2}))
 * `Like`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Like{"a", "c"})
 // a LIKE ? [%c%]
@@ -129,7 +126,7 @@ sql, args, _ := ToSQL(Like{"a", "c"})
 * `Expr` you can customerize your sql with `Expr`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Expr("a = ? ", 1))
 // a = ? [1]
@@ -140,7 +137,7 @@ sql, args, _ := ToSQL(Eq{"a": Expr("select id from table where c = ?", 1)})
 * `In` and `NotIn`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(In("a", 1, 2, 3))
 // a IN (?,?,?) [1,2,3]
@@ -153,7 +150,7 @@ sql, args, _ := ToSQL(In("a", Expr("select id from b where c = ?", 1))))
 * `IsNull` and `NotNull`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(IsNull{"a"})
 // a IS NULL []
@@ -164,7 +161,7 @@ sql, args, _ := ToSQL(NotNull{"b"})
 * `And(conds ...Cond)`, And can connect one or more condtions via And
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
@@ -173,7 +170,7 @@ sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 * `Or(conds ...Cond)`, Or can connect one or more conditions via Or
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
 // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
@@ -184,7 +181,7 @@ sql, args, _ := ToSQL(Or(Eq{"a":1}, And(Like{"b", "c"}, Neq{"d", 2})))
 * `Between`
 
 ```Go
-import . "github.com/go-xorm/builder"
+import . "github.com/xormplus/builder"
 
 sql, args, _ := ToSQL(Between{"a", 1, 2})
 // a BETWEEN 1 AND 2
@@ -203,4 +200,4 @@ type Cond interface {
 }
 ```
 
-You can define yourself conditions and compose with other `Cond`.
+You can define yourself conditions and compose with other `Cond`.

+ 5 - 5
builder.go

@@ -60,7 +60,7 @@ type Builder struct {
 	limitation *limit
 	insertCols []string
 	insertVals []interface{}
-	updates    []Eq
+	updates    []UpdateCond
 	orderBy    string
 	groupBy    string
 	having     string
@@ -302,11 +302,11 @@ func (b *Builder) Insert(eq ...interface{}) *Builder {
 }
 
 // Update sets update SQL
-func (b *Builder) Update(updates ...Eq) *Builder {
-	b.updates = make([]Eq, 0, len(updates))
+func (b *Builder) Update(updates ...Cond) *Builder {
+	b.updates = make([]UpdateCond, 0, len(updates))
 	for _, update := range updates {
-		if update.IsValid() {
-			b.updates = append(b.updates, update)
+		if u, ok := update.(UpdateCond); ok && u.IsValid() {
+			b.updates = append(b.updates, u)
 		}
 	}
 	b.optype = updateType

+ 9 - 2
builder_update.go

@@ -8,8 +8,14 @@ import (
 	"fmt"
 )
 
+// UpdateCond defines an interface that cond could be used with update
+type UpdateCond interface {
+	IsValid() bool
+	OpWriteTo(op string, w Writer) error
+}
+
 // Update creates an update Builder
-func Update(updates ...Eq) *Builder {
+func Update(updates ...Cond) *Builder {
 	builder := &Builder{cond: NewCond()}
 	return builder.Update(updates...)
 }
@@ -27,7 +33,8 @@ func (b *Builder) updateWriteTo(w Writer) error {
 	}
 
 	for i, s := range b.updates {
-		if err := s.opWriteTo(",", w); err != nil {
+
+		if err := s.OpWriteTo(",", w); err != nil {
 			return err
 		}
 

+ 5 - 0
builder_update_test.go

@@ -49,4 +49,9 @@ func TestBuilderUpdate(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "UPDATE table1 SET a=?,b=? WHERE a=?", sql)
 	assert.EqualValues(t, []interface{}{2, 1, 1}, args)
+
+	sql, args, err = Update(Eq{"a": 1}, Expr("c = c+1")).From("table1").Where(Eq{"b": 2}).ToSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "UPDATE table1 SET a=?,c = c+1 WHERE b=?", sql)
+	assert.EqualValues(t, []interface{}{1, 2}, args)
 }

+ 3 - 2
cond_eq.go

@@ -20,7 +20,8 @@ type Eq map[string]interface{}
 
 var _ Cond = Eq{}
 
-func (eq Eq) opWriteTo(op string, w Writer) error {
+// OpWriteTo writes conditions with special operator
+func (eq Eq) OpWriteTo(op string, w Writer) error {
 	var i = 0
 	for _, k := range eq.sortedKeys() {
 		v := eq[k]
@@ -81,7 +82,7 @@ func (eq Eq) opWriteTo(op string, w Writer) error {
 
 // WriteTo writes SQL to Writer
 func (eq Eq) WriteTo(w Writer) error {
-	return eq.opWriteTo(" AND ", w)
+	return eq.OpWriteTo(" AND ", w)
 }
 
 // And implements And with other conditions

+ 4 - 0
cond_expr.go

@@ -18,6 +18,10 @@ func Expr(sql string, args ...interface{}) Cond {
 	return expr{sql, args}
 }
 
+func (expr expr) OpWriteTo(op string, w Writer) error {
+	return expr.WriteTo(w)
+}
+
 func (expr expr) WriteTo(w Writer) error {
 	if _, err := fmt.Fprint(w, expr.sql); err != nil {
 		return err

+ 49 - 0
cond_if.go

@@ -0,0 +1,49 @@
+// Copyright 2019 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package builder
+
+type condIf struct {
+	condition bool
+	condTrue  Cond
+	condFalse Cond
+}
+
+var _ Cond = condIf{}
+
+// If returns Cond via condition
+func If(condition bool, condTrue Cond, condFalse ...Cond) Cond {
+	var c = condIf{
+		condition: condition,
+		condTrue:  condTrue,
+	}
+	if len(condFalse) > 0 {
+		c.condFalse = condFalse[0]
+	}
+	return c
+}
+
+func (condIf condIf) WriteTo(w Writer) error {
+	if condIf.condition {
+		return condIf.condTrue.WriteTo(w)
+	} else if condIf.condFalse != nil {
+		return condIf.condFalse.WriteTo(w)
+	}
+	return nil
+}
+
+func (condIf condIf) And(conds ...Cond) Cond {
+	return And(condIf, And(conds...))
+}
+
+func (condIf condIf) Or(conds ...Cond) Cond {
+	return Or(condIf, Or(conds...))
+}
+
+func (condIf condIf) IsValid() bool {
+	if condIf.condition {
+		return condIf.condTrue != nil
+	}
+	return condIf.condFalse != nil
+}

+ 38 - 0
cond_if_test.go

@@ -0,0 +1,38 @@
+// Copyright 2019 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package builder
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCond_If(t *testing.T) {
+	cond1 := If(1 > 0, Eq{"a": 1}, Eq{"b": 1})
+	sql, err := ToBoundSQL(cond1)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "a=1", sql)
+
+	cond2 := If(1 < 0, Eq{"a": 1}, Eq{"b": 1})
+	sql, err = ToBoundSQL(cond2)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "b=1", sql)
+
+	cond3 := If(1 > 0, cond2, Eq{"c": 1})
+	sql, err = ToBoundSQL(cond3)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "b=1", sql)
+
+	cond4 := If(2 < 0, Eq{"d": "a"})
+	sql, err = ToBoundSQL(cond4)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "", sql)
+
+	cond5 := And(cond1, cond2, cond3, cond4)
+	sql, err = ToBoundSQL(cond5)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "a=1 AND b=1 AND b=1", sql)
+}

+ 11 - 11
doc.go

@@ -8,13 +8,13 @@ Package builder is a simple and powerful sql builder for Go.
 
 Make sure you have installed Go 1.1+ and then:
 
-    go get github.com/go-xorm/builder
+    go get github.com/xormplus/builder
 
 WARNNING: Currently, only query conditions are supported. Below is the supported conditions.
 
 1. Eq is a redefine of a map, you can give one or more conditions to Eq
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Eq{"a":1})
     // a=? [1]
@@ -31,7 +31,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 2. Neq is the same to Eq
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Neq{"a":1})
     // a<>? [1]
@@ -48,7 +48,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 3. Gt, Gte, Lt, Lte
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Gt{"a", 1}.And(Gte{"b", 2}))
     // a>? AND b>=? [1, 2]
@@ -57,14 +57,14 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 4. Like
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Like{"a", "c"})
     // a LIKE ? [%c%]
 
 5. Expr you can customerize your sql with Expr
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Expr("a = ? ", 1))
     // a = ? [1]
@@ -73,7 +73,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 6. In and NotIn
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(In("a", 1, 2, 3))
     // a IN (?,?,?) [1,2,3]
@@ -84,7 +84,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 7. IsNull and NotNull
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(IsNull{"a"})
     // a IS NULL []
@@ -93,14 +93,14 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 8. And(conds ...Cond), And can connect one or more condtions via AND
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(And(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     // a=? AND b LIKE ? AND d<>? [1, %c%, 2]
 
 9. Or(conds ...Cond), Or can connect one or more conditions via Or
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Or(Eq{"a":1}, Like{"b", "c"}, Neq{"d", 2}))
     // a=? OR b LIKE ? OR d<>? [1, %c%, 2]
@@ -109,7 +109,7 @@ WARNNING: Currently, only query conditions are supported. Below is the supported
 
 10. Between
 
-    import . "github.com/go-xorm/builder"
+    import . "github.com/xormplus/builder"
 
     sql, args, _ := ToSQL(Between("a", 1, 2))
     // a BETWEEN 1 AND 2

+ 5 - 0
go.mod

@@ -1 +1,6 @@
 module "github.com/xormplus/builder"
+
+require (
+	github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a
+	github.com/stretchr/testify v1.3.0
+)

+ 5 - 1
sql.go

@@ -8,6 +8,7 @@ import (
 	sql2 "database/sql"
 	"fmt"
 	"reflect"
+	"strings"
 	"time"
 )
 
@@ -116,7 +117,10 @@ func ConvertToBoundSQL(sql string, args []interface{}) (string, error) {
 			if noSQLQuoteNeeded(arg) {
 				_, err = fmt.Fprint(&buf, arg)
 			} else {
-				_, err = fmt.Fprintf(&buf, "'%v'", arg)
+				// replace ' -> '' (standard replacement) to avoid critical SQL injection,
+				// NOTICE: may allow some injection like % (or _) in LIKE query
+				_, err = fmt.Fprintf(&buf, "'%v'", strings.Replace(fmt.Sprintf("%v", arg), "'",
+					"''", -1))
 			}
 			if err != nil {
 				return "", err

+ 6 - 0
sql_test.go

@@ -207,3 +207,9 @@ func TestToSQLInDifferentDialects(t *testing.T) {
 	assert.EqualValues(t, "SELECT * FROM table1 WHERE a=? AND b<>?", sql)
 	assert.EqualValues(t, []interface{}{"1", "100"}, args)
 }
+
+func TestToSQLInjectionHarmlessDisposal(t *testing.T) {
+	sql, err := MySQL().Select("*").From("table1").Where(Cond(Eq{"name": "cat';truncate table table1;"})).ToBoundSQL()
+	assert.NoError(t, err)
+	assert.EqualValues(t, "SELECT * FROM table1 WHERE name='cat'';truncate table table1;'", sql)
+}