build(deps): bump github.com/Microsoft/hcsshim from 0.9.9 to 0.11.1
Bumps [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) from 0.9.9 to 0.11.1. - [Release notes](https://github.com/Microsoft/hcsshim/releases) - [Commits](https://github.com/Microsoft/hcsshim/compare/v0.9.9...v0.11.1) --- updated-dependencies: - dependency-name: github.com/Microsoft/hcsshim dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
118
vendor/github.com/Microsoft/hcsshim/internal/log/context.go
generated
vendored
Normal file
118
vendor/github.com/Microsoft/hcsshim/internal/log/context.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type entryContextKeyType int
|
||||
|
||||
const _entryContextKey entryContextKeyType = iota
|
||||
|
||||
var (
|
||||
// L is the default, blank logging entry. WithField and co. all return a copy
|
||||
// of the original entry, so this will not leak fields between calls.
|
||||
//
|
||||
// Do NOT modify fields directly, as that will corrupt state for all users and
|
||||
// is not thread safe.
|
||||
// Instead, use `L.With*` or `L.Dup()`. Or `G(context.Background())`.
|
||||
L = logrus.NewEntry(logrus.StandardLogger())
|
||||
|
||||
// G is an alias for GetEntry
|
||||
G = GetEntry
|
||||
|
||||
// S is an alias for SetEntry
|
||||
S = SetEntry
|
||||
|
||||
// U is an alias for UpdateContext
|
||||
U = UpdateContext
|
||||
)
|
||||
|
||||
// GetEntry returns a `logrus.Entry` stored in the context, if one exists.
|
||||
// Otherwise, it returns a default entry that points to the current context.
|
||||
//
|
||||
// Note: if the a new entry is returned, it will reference the passed in context.
|
||||
// However, existing contexts may be stored in parent contexts and additionally reference
|
||||
// earlier contexts.
|
||||
// Use `UpdateContext` to update the entry and context.
|
||||
func GetEntry(ctx context.Context) *logrus.Entry {
|
||||
entry := fromContext(ctx)
|
||||
|
||||
if entry == nil {
|
||||
entry = L.WithContext(ctx)
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// SetEntry updates the log entry in the context with the provided fields, and
|
||||
// returns both. It is equivalent to:
|
||||
//
|
||||
// entry := GetEntry(ctx).WithFields(fields)
|
||||
// ctx = WithContext(ctx, entry)
|
||||
//
|
||||
// See WithContext for more information.
|
||||
func SetEntry(ctx context.Context, fields logrus.Fields) (context.Context, *logrus.Entry) {
|
||||
e := GetEntry(ctx)
|
||||
if len(fields) > 0 {
|
||||
e = e.WithFields(fields)
|
||||
}
|
||||
return WithContext(ctx, e)
|
||||
}
|
||||
|
||||
// UpdateContext extracts the log entry from the context, and, if the entry's
|
||||
// context points to a parent's of the current context, ands the entry
|
||||
// to the most recent context. It is equivalent to:
|
||||
//
|
||||
// entry := GetEntry(ctx)
|
||||
// ctx = WithContext(ctx, entry)
|
||||
//
|
||||
// This allows the entry to reference the most recent context and any new
|
||||
// values (such as span contexts) added to it.
|
||||
//
|
||||
// See WithContext for more information.
|
||||
func UpdateContext(ctx context.Context) context.Context {
|
||||
// there is no way to check its ctx (and not one of its parents) that contains `e`
|
||||
// so, at a slight cost, force add `e` to the context
|
||||
ctx, _ = WithContext(ctx, GetEntry(ctx))
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithContext returns a context that contains the provided log entry.
|
||||
// The entry can be extracted with `GetEntry` (`G`)
|
||||
//
|
||||
// The entry in the context is a copy of `entry` (generated by `entry.WithContext`)
|
||||
func WithContext(ctx context.Context, entry *logrus.Entry) (context.Context, *logrus.Entry) {
|
||||
// regardless of the order, entry.Context != GetEntry(ctx)
|
||||
// here, the returned entry will reference the supplied context
|
||||
entry = entry.WithContext(ctx)
|
||||
ctx = context.WithValue(ctx, _entryContextKey, entry)
|
||||
|
||||
return ctx, entry
|
||||
}
|
||||
|
||||
// Copy extracts the tracing Span and logging entry from the src Context, if they
|
||||
// exist, and adds them to the dst Context.
|
||||
//
|
||||
// This is useful to share tracing and logging between contexts, but not the
|
||||
// cancellation. For example, if the src Context has been cancelled but cleanup
|
||||
// operations triggered by the cancellation require a non-cancelled context to
|
||||
// execute.
|
||||
func Copy(dst context.Context, src context.Context) context.Context {
|
||||
if s := trace.FromContext(src); s != nil {
|
||||
dst = trace.NewContext(dst, s)
|
||||
}
|
||||
|
||||
if e := fromContext(src); e != nil {
|
||||
dst, _ = WithContext(dst, e)
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func fromContext(ctx context.Context) *logrus.Entry {
|
||||
e, _ := ctx.Value(_entryContextKey).(*logrus.Entry)
|
||||
return e
|
||||
}
|
85
vendor/github.com/Microsoft/hcsshim/internal/log/format.go
generated
vendored
Normal file
85
vendor/github.com/Microsoft/hcsshim/internal/log/format.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
)
|
||||
|
||||
const TimeFormat = log.RFC3339NanoFixed
|
||||
|
||||
func FormatTime(t time.Time) string {
|
||||
return t.Format(TimeFormat)
|
||||
}
|
||||
|
||||
// DurationFormat formats a [time.Duration] log entry.
|
||||
//
|
||||
// A nil value signals an error with the formatting.
|
||||
type DurationFormat func(time.Duration) interface{}
|
||||
|
||||
func DurationFormatString(d time.Duration) interface{} { return d.String() }
|
||||
func DurationFormatSeconds(d time.Duration) interface{} { return d.Seconds() }
|
||||
func DurationFormatMilliseconds(d time.Duration) interface{} { return d.Milliseconds() }
|
||||
|
||||
// FormatIO formats net.Conn and other types that have an `Addr()` or `Name()`.
|
||||
//
|
||||
// See FormatEnabled for more information.
|
||||
func FormatIO(ctx context.Context, v interface{}) string {
|
||||
m := make(map[string]string)
|
||||
m["type"] = reflect.TypeOf(v).String()
|
||||
|
||||
switch t := v.(type) {
|
||||
case net.Conn:
|
||||
m["localAddress"] = formatAddr(t.LocalAddr())
|
||||
m["remoteAddress"] = formatAddr(t.RemoteAddr())
|
||||
case interface{ Addr() net.Addr }:
|
||||
m["address"] = formatAddr(t.Addr())
|
||||
default:
|
||||
return Format(ctx, t)
|
||||
}
|
||||
|
||||
return Format(ctx, m)
|
||||
}
|
||||
|
||||
func formatAddr(a net.Addr) string {
|
||||
return a.Network() + "://" + a.String()
|
||||
}
|
||||
|
||||
// Format formats an object into a JSON string, without any indendtation or
|
||||
// HTML escapes.
|
||||
// Context is used to output a log waring if the conversion fails.
|
||||
//
|
||||
// This is intended primarily for `trace.StringAttribute()`
|
||||
func Format(ctx context.Context, v interface{}) string {
|
||||
b, err := encode(v)
|
||||
if err != nil {
|
||||
G(ctx).WithError(err).Warning("could not format value")
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func encode(v interface{}) ([]byte, error) {
|
||||
return encodeBuffer(&bytes.Buffer{}, v)
|
||||
}
|
||||
|
||||
func encodeBuffer(buf *bytes.Buffer, v interface{}) ([]byte, error) {
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", "")
|
||||
|
||||
if err := enc.Encode(v); err != nil {
|
||||
err = fmt.Errorf("could not marshall %T to JSON for logging: %w", v, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encoder.Encode appends a newline to the end
|
||||
return bytes.TrimSpace(buf.Bytes()), nil
|
||||
}
|
23
vendor/github.com/Microsoft/hcsshim/internal/log/g.go
generated
vendored
23
vendor/github.com/Microsoft/hcsshim/internal/log/g.go
generated
vendored
@ -1,23 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// G returns a `logrus.Entry` with the `TraceID, SpanID` from `ctx` if `ctx`
|
||||
// contains an OpenCensus `trace.Span`.
|
||||
func G(ctx context.Context) *logrus.Entry {
|
||||
span := trace.FromContext(ctx)
|
||||
if span != nil {
|
||||
sctx := span.SpanContext()
|
||||
return logrus.WithFields(logrus.Fields{
|
||||
"traceID": sctx.TraceID.String(),
|
||||
"spanID": sctx.SpanID.String(),
|
||||
// "parentSpanID": TODO: JTERRY75 - Try to convince OC to export this?
|
||||
})
|
||||
}
|
||||
return logrus.NewEntry(logrus.StandardLogger())
|
||||
}
|
174
vendor/github.com/Microsoft/hcsshim/internal/log/hook.go
generated
vendored
Normal file
174
vendor/github.com/Microsoft/hcsshim/internal/log/hook.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/logfields"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
const nullString = "null"
|
||||
|
||||
// Hook intercepts and formats a [logrus.Entry] before it logged.
|
||||
//
|
||||
// The shim either outputs the logs through an ETW hook, discarding the (formatted) output
|
||||
// or logs output to a pipe for logging binaries to consume.
|
||||
// The Linux GCS outputs logrus entries over stdout, which is then consumed and re-output
|
||||
// by the shim.
|
||||
type Hook struct {
|
||||
// EncodeAsJSON formats structs, maps, arrays, slices, and [bytes.Buffer] as JSON.
|
||||
// Variables of [bytes.Buffer] will be converted to []byte.
|
||||
//
|
||||
// Default is false.
|
||||
EncodeAsJSON bool
|
||||
|
||||
// FormatTime specifies the format for [time.Time] variables.
|
||||
// An empty string disables formatting.
|
||||
// When disabled, the fall back will the JSON encoding, if enabled.
|
||||
//
|
||||
// Default is [github.com/containerd/containerd/log.RFC3339NanoFixed].
|
||||
TimeFormat string
|
||||
|
||||
// Duration format converts a [time.Duration] fields to an appropriate encoding.
|
||||
// nil disables formatting.
|
||||
// When disabled, the fall back will the JSON encoding, if enabled.
|
||||
//
|
||||
// Default is [DurationFormatString], which appends a duration unit after the value.
|
||||
DurationFormat DurationFormat
|
||||
|
||||
// AddSpanContext adds [logfields.TraceID] and [logfields.SpanID] fields to
|
||||
// the entry from the span context stored in [logrus.Entry.Context], if it exists.
|
||||
AddSpanContext bool
|
||||
}
|
||||
|
||||
var _ logrus.Hook = &Hook{}
|
||||
|
||||
func NewHook() *Hook {
|
||||
return &Hook{
|
||||
TimeFormat: log.RFC3339NanoFixed,
|
||||
DurationFormat: DurationFormatString,
|
||||
AddSpanContext: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func (h *Hook) Fire(e *logrus.Entry) (err error) {
|
||||
// JSON encode, if necessary, then add span information
|
||||
h.encode(e)
|
||||
h.addSpanContext(e)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encode loops through all the fields in the [logrus.Entry] and encodes them according to
|
||||
// the settings in [Hook].
|
||||
// If [Hook.TimeFormat] is non-empty, it will be passed to [time.Time.Format] for
|
||||
// fields of type [time.Time].
|
||||
//
|
||||
// If [Hook.EncodeAsJSON] is true, then fields that are not numeric, boolean, strings, or
|
||||
// errors will be encoded via a [json.Marshal] (with HTML escaping disabled).
|
||||
// Chanel- and function-typed fields, as well as unsafe pointers are left alone and not encoded.
|
||||
//
|
||||
// If [Hook.TimeFormat] and [Hook.DurationFormat] are empty and [Hook.EncodeAsJSON] is false,
|
||||
// then this is a no-op.
|
||||
func (h *Hook) encode(e *logrus.Entry) {
|
||||
d := e.Data
|
||||
|
||||
formatTime := h.TimeFormat != ""
|
||||
formatDuration := h.DurationFormat != nil
|
||||
if !(h.EncodeAsJSON || formatTime || formatDuration) {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range d {
|
||||
// encode types with dedicated formatting options first
|
||||
|
||||
if vv, ok := v.(time.Time); formatTime && ok {
|
||||
d[k] = vv.Format(h.TimeFormat)
|
||||
continue
|
||||
}
|
||||
|
||||
if vv, ok := v.(time.Duration); formatDuration && ok {
|
||||
d[k] = h.DurationFormat(vv)
|
||||
continue
|
||||
}
|
||||
|
||||
// general case JSON encoding
|
||||
|
||||
if !h.EncodeAsJSON {
|
||||
continue
|
||||
}
|
||||
|
||||
switch vv := v.(type) {
|
||||
// built in types
|
||||
// "json" marshals errors as "{}", so leave alone here
|
||||
case bool, string, error, uintptr,
|
||||
int8, int16, int32, int64, int,
|
||||
uint8, uint32, uint64, uint,
|
||||
float32, float64:
|
||||
continue
|
||||
|
||||
// Rather than setting d[k] = vv.String(), JSON encode []byte value, since it
|
||||
// may be a binary payload and not representable as a string.
|
||||
// `case bytes.Buffer,*bytes.Buffer:` resolves `vv` to `interface{}`,
|
||||
// so cannot use `vv.Bytes`.
|
||||
// Could move to below the `reflect.Indirect()` call below, but
|
||||
// that would require additional typematching and dereferencing.
|
||||
// Easier to keep these duplicate branches here.
|
||||
case bytes.Buffer:
|
||||
v = vv.Bytes()
|
||||
case *bytes.Buffer:
|
||||
v = vv.Bytes()
|
||||
}
|
||||
|
||||
// dereference pointer or interface variables
|
||||
rv := reflect.Indirect(reflect.ValueOf(v))
|
||||
// check if `v` is a null pointer
|
||||
if !rv.IsValid() {
|
||||
d[k] = nullString
|
||||
continue
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Map, reflect.Struct, reflect.Array, reflect.Slice:
|
||||
default:
|
||||
// Bool, [U]?Int*, Float*, Complex*, Uintptr, String: encoded as normal
|
||||
// Chan, Func: not supported by json
|
||||
// Interface, Pointer: dereferenced above
|
||||
// UnsafePointer: not supported by json, not safe to de-reference; leave alone
|
||||
continue
|
||||
}
|
||||
|
||||
b, err := encode(v)
|
||||
if err != nil {
|
||||
// Errors are written to stderr (ie, to `panic.log`) and stops the remaining
|
||||
// hooks (ie, exporting to ETW) from firing. So add encoding errors to
|
||||
// the entry data to be written out, but keep on processing.
|
||||
d[k+"-"+logrus.ErrorKey] = err.Error()
|
||||
// keep the original `v` as the value,
|
||||
continue
|
||||
}
|
||||
d[k] = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hook) addSpanContext(e *logrus.Entry) {
|
||||
ctx := e.Context
|
||||
if !h.AddSpanContext || ctx == nil {
|
||||
return
|
||||
}
|
||||
span := trace.FromContext(ctx)
|
||||
if span == nil {
|
||||
return
|
||||
}
|
||||
sctx := span.SpanContext()
|
||||
e.Data[logfields.TraceID] = sctx.TraceID.String()
|
||||
e.Data[logfields.SpanID] = sctx.SpanID.String()
|
||||
}
|
184
vendor/github.com/Microsoft/hcsshim/internal/log/scrub.go
generated
vendored
Normal file
184
vendor/github.com/Microsoft/hcsshim/internal/log/scrub.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
||||
)
|
||||
|
||||
// This package scrubs objects of potentially sensitive information to pass to logging
|
||||
|
||||
type genMap = map[string]interface{}
|
||||
type scrubberFunc func(genMap) error
|
||||
|
||||
const _scrubbedReplacement = "<scrubbed>"
|
||||
|
||||
var (
|
||||
ErrUnknownType = errors.New("encoded object is of unknown type")
|
||||
|
||||
// case sensitive keywords, so "env" is not a substring on "Environment"
|
||||
_scrubKeywords = [][]byte{[]byte("env"), []byte("Environment")}
|
||||
|
||||
_scrub int32
|
||||
)
|
||||
|
||||
// SetScrubbing enables scrubbing
|
||||
func SetScrubbing(enable bool) {
|
||||
v := int32(0) // cant convert from bool to int32 directly
|
||||
if enable {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&_scrub, v)
|
||||
}
|
||||
|
||||
// IsScrubbingEnabled checks if scrubbing is enabled
|
||||
func IsScrubbingEnabled() bool {
|
||||
v := atomic.LoadInt32(&_scrub)
|
||||
return v != 0
|
||||
}
|
||||
|
||||
// ScrubProcessParameters scrubs HCS Create Process requests with config parameters of
|
||||
// type internal/hcs/schema2.ScrubProcessParameters (aka hcsshema.ScrubProcessParameters)
|
||||
func ScrubProcessParameters(s string) (string, error) {
|
||||
// todo: deal with v1 ProcessConfig
|
||||
b := []byte(s)
|
||||
if !IsScrubbingEnabled() || !hasKeywords(b) || !json.Valid(b) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
pp := hcsschema.ProcessParameters{}
|
||||
if err := json.Unmarshal(b, &pp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
pp.Environment = map[string]string{_scrubbedReplacement: _scrubbedReplacement}
|
||||
|
||||
b, err := encodeBuffer(bytes.NewBuffer(b[:0]), pp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// ScrubBridgeCreate scrubs requests sent over the bridge of type
|
||||
// internal/gcs/protocol.containerCreate wrapping an internal/hcsoci.linuxHostedSystem
|
||||
func ScrubBridgeCreate(b []byte) ([]byte, error) {
|
||||
return scrubBytes(b, scrubBridgeCreate)
|
||||
}
|
||||
|
||||
func scrubBridgeCreate(m genMap) error {
|
||||
if !isRequestBase(m) {
|
||||
return ErrUnknownType
|
||||
}
|
||||
if ss, ok := m["ContainerConfig"]; ok {
|
||||
// ContainerConfig is a json encoded struct passed as a regular string field
|
||||
s, ok := ss.(string)
|
||||
if !ok {
|
||||
return ErrUnknownType
|
||||
}
|
||||
b, err := scrubBytes([]byte(s), scrubLinuxHostedSystem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m["ContainerConfig"] = string(b)
|
||||
return nil
|
||||
}
|
||||
return ErrUnknownType
|
||||
}
|
||||
|
||||
func scrubLinuxHostedSystem(m genMap) error {
|
||||
if m, ok := index(m, "OciSpecification"); ok {
|
||||
if _, ok := m["annotations"]; ok {
|
||||
m["annotations"] = map[string]string{_scrubbedReplacement: _scrubbedReplacement}
|
||||
}
|
||||
if m, ok := index(m, "process"); ok {
|
||||
if _, ok := m["env"]; ok {
|
||||
m["env"] = []string{_scrubbedReplacement}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return ErrUnknownType
|
||||
}
|
||||
|
||||
// ScrubBridgeExecProcess scrubs requests sent over the bridge of type
|
||||
// internal/gcs/protocol.containerExecuteProcess
|
||||
func ScrubBridgeExecProcess(b []byte) ([]byte, error) {
|
||||
return scrubBytes(b, scrubExecuteProcess)
|
||||
}
|
||||
|
||||
func scrubExecuteProcess(m genMap) error {
|
||||
if !isRequestBase(m) {
|
||||
return ErrUnknownType
|
||||
}
|
||||
if m, ok := index(m, "Settings"); ok {
|
||||
if ss, ok := m["ProcessParameters"]; ok {
|
||||
// ProcessParameters is a json encoded struct passed as a regular sting field
|
||||
s, ok := ss.(string)
|
||||
if !ok {
|
||||
return ErrUnknownType
|
||||
}
|
||||
|
||||
s, err := ScrubProcessParameters(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m["ProcessParameters"] = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrUnknownType
|
||||
}
|
||||
|
||||
func scrubBytes(b []byte, scrub scrubberFunc) ([]byte, error) {
|
||||
if !IsScrubbingEnabled() || !hasKeywords(b) || !json.Valid(b) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
m := make(genMap)
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// could use regexp, but if the env strings contain braces, the regexp fails
|
||||
// parsing into individual structs would require access to private structs
|
||||
if err := scrub(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := encode(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func isRequestBase(m genMap) bool {
|
||||
// neither of these are (currently) `omitempty`
|
||||
_, a := m["ActivityId"]
|
||||
_, c := m["ContainerId"]
|
||||
return a && c
|
||||
}
|
||||
|
||||
// combination `m, ok := m[s]` and `m, ok := m.(genMap)`
|
||||
func index(m genMap, s string) (genMap, bool) {
|
||||
if m, ok := m[s]; ok {
|
||||
mm, ok := m.(genMap)
|
||||
return mm, ok
|
||||
}
|
||||
|
||||
return m, false
|
||||
}
|
||||
|
||||
func hasKeywords(b []byte) bool {
|
||||
for _, bb := range _scrubKeywords {
|
||||
if bytes.Contains(b, bb) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user