github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safesql/safesql.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package safesql implements a safe version of the standard sql package while trying to keep the API as similar as
    16  // possible to the original one.
    17  // The concept of this package is to provide "safe by construction" SQL strings so that code that would accidentally introduce
    18  // SQL injection vulnerabilities does not compile.
    19  // If uncheckedconversions and legacyconversions are not used and the sql package is forbidden this package guarantees that only compile-time constants will be
    20  // interpreted as SQL, thus preventing attacker-controlled strings to be accidentally executed.
    21  //
    22  // # Migration Examples
    23  //
    24  // Code like the following is trivial to migrate from sql to safesql:
    25  //
    26  //	db.Query("SELECT ...", args...)
    27  //
    28  // The only change required would be to promote the string literal to a trusted string:
    29  //
    30  //	db.Query(safesql.New("SELECT ..."), args...)
    31  //
    32  // For more complicated cases it might be needed to use the helper functions like Join and Concat.
    33  // If the queries for the service are stored in a trusted runtime-only source that cannot be controlled by a user
    34  // the uncheckedconversions package can be used to assert that those strings are under the programmer control.
    35  // Note that unchecked conversions should be very limited, ideally never used, as they pose a security risk.
    36  //
    37  // Note on API documentation.
    38  //
    39  // For documentation on methods and types that wrap the standard ones please refer to the stdlib package doc instead, as
    40  // all the types exported by this package are tiny wrappers around the standard ones and thus follow their behavior.
    41  // The only relevant difference is that functions accept TrustedSQLString instances instead of plain "strings" and that some
    42  // dangerous methods have been removed.
    43  //
    44  // # Explainer
    45  //
    46  // This package wraps the sql package and all methods that would normally take a string take a TrustedSQLString instead.
    47  // The constructor for TrustedSQLString takes a stringConstant as an argument, which is an unexported type constituted by a named string.
    48  // The only way for a package outside of safesql to construct a TrustedSQLString is thus to pass an untyped string (only const strings can be untyped) to the constructor.
    49  package safesql
    50  
    51  import (
    52  	"strconv"
    53  	"strings"
    54  
    55  	"github.com/google/go-safeweb/safesql/internal/raw"
    56  )
    57  
    58  func init() {
    59  	// Initialize the bypass mechanisms for unchecked and legacy conversions.
    60  	raw.TrustedSQLString = func(unsafe string) TrustedSQLString { return TrustedSQLString{unsafe} }
    61  }
    62  
    63  type stringConstant string
    64  
    65  // TrustedSQLString is a string representing a SQL query that is known to be safe and not contain potentially malicious inputs.
    66  type TrustedSQLString struct {
    67  	s string
    68  }
    69  
    70  // New constructs a TrustedSQLString from a compile-time constant string.
    71  // Since the stringConstant type is unexported the only way to call this function outside of this package is to pass
    72  // a string literal or an untyped string const.
    73  func New(text stringConstant) TrustedSQLString { return TrustedSQLString{string(text)} }
    74  
    75  // NewFromUint64 constructs a TrustedSQLString from a uint64.
    76  func NewFromUint64(i uint64) TrustedSQLString { return TrustedSQLString{strconv.FormatUint(i, 10)} }
    77  
    78  // TrustedSQLStringConcat concatenates the given trusted SQL strings into a trusted string.
    79  //
    80  // Note: this function should not be abused to create arbitrary queries from user input, it is just
    81  // intended as a helper to compose queries at runtime to avoid redundant constants.
    82  func TrustedSQLStringConcat(ss ...TrustedSQLString) TrustedSQLString {
    83  	return TrustedSQLStringJoin(ss, TrustedSQLString{})
    84  }
    85  
    86  // TrustedSQLStringJoin joins the given trusted SQL with the given separator the same way strings.Join would.
    87  //
    88  // Note: this function should not be abused to create arbitrary queries from user input, it is just
    89  // intended as a helper to compose queries at runtime to avoid redundant constants.
    90  func TrustedSQLStringJoin(ss []TrustedSQLString, sep TrustedSQLString) TrustedSQLString {
    91  	accum := make([]string, 0, len(ss))
    92  	for _, s := range ss {
    93  		accum = append(accum, s.s)
    94  	}
    95  	return TrustedSQLString{strings.Join(accum, sep.s)}
    96  }
    97  func (t TrustedSQLString) String() string {
    98  	return t.s
    99  }
   100  
   101  // TrustedSQLStringSplit functions as strings.Split but for TrustedSQLStrings.
   102  func TrustedSQLStringSplit(s TrustedSQLString, sep TrustedSQLString) []TrustedSQLString {
   103  	spl := strings.Split(s.s, sep.s)
   104  	accum := make([]TrustedSQLString, 0, len(spl))
   105  	for _, s := range spl {
   106  		accum = append(accum, TrustedSQLString{s})
   107  	}
   108  	return accum
   109  }