Configurable implementation hidden behind a contract

Some concrete implementations are better to be hidden behind contracts, in a specific package. A layer of abstraction can save you later, mostly on unstable projects that often change requirements, or maybe you just want to test an idea, maybe you don’t have the necessary time to finish a task in a way you’d like to.

A good contract will save you. You can “throw” your implementation behind it and come back later to refine it. Sometimes later means in a few years. But if you’re behind a well designed contract, most probably you’re going to alter only the concrete implementation, without touching large areas of the projects.

I had to filter some user input. Some strings had to be HTML escaped, some sanitized to prevent different attacks. I’ve wrapped everything into a package, behind a contract. For escaping I’ve used Go’s html.EscapeString function, while for sanitizing I’ve found theĀ bluemonday package, which is inspired by an OWASP sanitizier for Java.

The contract is clean and simple:

package sanitizer

type Sanitizer interface {
   EscapeString(s string) string
   SanitizeString(s string, policies ...interface{}) string
}

Nothing fancy for EscapeString, it just wraps html.EscapeString. But SanitizeStrings wraps the bluemonday package (in this case, a small part of it). I don’t want too much coupling to bluemonday outside my sanitizer package, at the API level. Maybe one day I decide to rely on another package instead of bluemonday. Also, bluemonday is based on different configurable policies that I wanted to be configurable from outside.

Here’s my version of a configurable concrete implementation hidden behind a contract:

package sanitizer

import (
   "html"
   "github.com/microcosm-cc/bluemonday"
)

type Sanitizer interface {
   EscapeString(s string) string
   SanitizeString(s string, policies ...interface{}) string
}

type stringSanitizer struct {
   UGCPolicy *bluemonday.Policy
}

func (ss *stringSanitizer) EscapeString(s string) string {
   return html.EscapeString(s)
}

type URLPolicy struct {
   Schemes []string
}
type NoAttrsPolicy struct {
}

func (ss *stringSanitizer) SanitizeString(s string, policies ...interface{}) string {
   p := ss.UGCPolicy
   for _, pol := range policies {
      switch pol := pol.(type) {
      case URLPolicy:
         p = p.AllowURLSchemes(pol.Schemes...)
         break
      case NoAttrsPolicy:
         p = p.AllowNoAttrs().Globally()
         break
      }
   }
   return p.Sanitize(s)
}

func New() Sanitizer {
   return &stringSanitizer{
      UGCPolicy: bluemonday.UGCPolicy(),
   }
}

Usage:

package main

import (
	"sanitizer"
	"fmt"
)

func main() {
	sanitize := sanitizer.New()

	s := sanitize.SanitizeString(
		`input string`,
		sanitizer.NoAttrsPolicy{},
		sanitizer.URLPolicy{Schemes:[]string{"http", "https"}},
	)

	fmt.Println(s)
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.