Skip to content

Commit

Permalink
Inject css rules into html document (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
AitakattaSora authored Jan 7, 2025
1 parent 6621c89 commit cdd2a16
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 2 deletions.
4 changes: 3 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/anfragment/zen/internal/certstore"
"github.com/anfragment/zen/internal/cfg"
"github.com/anfragment/zen/internal/cosmetic"
"github.com/anfragment/zen/internal/cssrule"
"github.com/anfragment/zen/internal/filter"
"github.com/anfragment/zen/internal/jsrule"
"github.com/anfragment/zen/internal/logger"
Expand Down Expand Up @@ -174,9 +175,10 @@ func (a *App) StartProxy() (err error) {
}

cosmeticRulesInjector := cosmetic.NewInjector()
cssRulesInjector := cssrule.NewInjector()
jsRuleInjector := jsrule.NewInjector()

filter, err := filter.NewFilter(a.config, ruleMatcher, exceptionRuleMatcher, scriptletInjector, cosmeticRulesInjector, jsRuleInjector, a.eventsHandler)
filter, err := filter.NewFilter(a.config, ruleMatcher, exceptionRuleMatcher, scriptletInjector, cosmeticRulesInjector, cssRulesInjector, jsRuleInjector, a.eventsHandler)
if err != nil {
return fmt.Errorf("create filter: %v", err)
}
Expand Down
78 changes: 78 additions & 0 deletions internal/cssrule/injector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cssrule

import (
"bytes"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"strings"

"github.com/anfragment/zen/internal/hostmatch"
"github.com/anfragment/zen/internal/htmlrewrite"
"github.com/anfragment/zen/internal/logger"
)

var (
RuleRegex = regexp.MustCompile(`.*#@?\$#.+`)
primaryRuleRegex = regexp.MustCompile(`(.*?)#\$#(.*)`)
exceptionRuleRegex = regexp.MustCompile(`(.*?)#@\$#(.+)`)

injectionStart = []byte("<style>")
injectionEnd = []byte("</style>")
)

type store interface {
AddPrimaryRule(hostnamePatterns string, css string) error
AddExceptionRule(hostnamePatterns string, css string) error
Get(hostname string) []string
}

type Injector struct {
store store
}

func NewInjector() *Injector {
return &Injector{
store: hostmatch.NewHostMatcher[string](),
}
}

func (inj *Injector) AddRule(rule string) error {
if match := primaryRuleRegex.FindStringSubmatch(rule); match != nil {
if err := inj.store.AddPrimaryRule(match[1], match[2]); err != nil {
return fmt.Errorf("add primary rule: %w", err)
}
return nil
}

if match := exceptionRuleRegex.FindStringSubmatch(rule); match != nil {
if err := inj.store.AddExceptionRule(match[1], match[2]); err != nil {
return fmt.Errorf("add exception rule: %w", err)
}
return nil
}

return errors.New("unsupported syntax")
}

func (inj *Injector) Inject(req *http.Request, res *http.Response) error {
hostname := req.URL.Hostname()
cssRules := inj.store.Get(hostname)
log.Printf("got %d css rules for %q", len(cssRules), logger.Redacted(hostname))
if len(cssRules) == 0 {
return nil
}

var ruleInjection bytes.Buffer
ruleInjection.Write(injectionStart)
ruleInjection.WriteString(strings.Join(cssRules, ""))
ruleInjection.Write(injectionEnd)

htmlrewrite.ReplaceHeadContents(res, func(match []byte) []byte {
return bytes.Join([][]byte{match, ruleInjection.Bytes()}, nil)
})

return nil
}
23 changes: 22 additions & 1 deletion internal/filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/anfragment/zen/internal/cfg"
"github.com/anfragment/zen/internal/cosmetic"
"github.com/anfragment/zen/internal/cssrule"
"github.com/anfragment/zen/internal/jsrule"
"github.com/anfragment/zen/internal/logger"
"github.com/anfragment/zen/internal/rule"
Expand Down Expand Up @@ -52,6 +53,11 @@ type cosmeticRulesInjector interface {
AddRule(string) error
}

type cssRulesInjector interface {
Inject(*http.Request, *http.Response) error
AddRule(string) error
}

type jsRuleInjector interface {
AddRule(rule string) error
Inject(*http.Request, *http.Response) error
Expand All @@ -66,6 +72,7 @@ type Filter struct {
exceptionRuleMatcher ruleMatcher
scriptletsInjector scriptletsInjector
cosmeticRulesInjector cosmeticRulesInjector
cssRulesInjector cssRulesInjector
jsRuleInjector jsRuleInjector
eventsEmitter filterEventsEmitter
}
Expand All @@ -80,7 +87,7 @@ var (
)

// NewFilter creates and initializes a new filter.
func NewFilter(config config, ruleMatcher ruleMatcher, exceptionRuleMatcher ruleMatcher, scriptletsInjector scriptletsInjector, cosmeticRulesInjector cosmeticRulesInjector, jsRuleInjector jsRuleInjector, eventsEmitter filterEventsEmitter) (*Filter, error) {
func NewFilter(config config, ruleMatcher ruleMatcher, exceptionRuleMatcher ruleMatcher, scriptletsInjector scriptletsInjector, cosmeticRulesInjector cosmeticRulesInjector, cssRulesInjector cssRulesInjector, jsRuleInjector jsRuleInjector, eventsEmitter filterEventsEmitter) (*Filter, error) {
if config == nil {
return nil, errors.New("config is nil")
}
Expand All @@ -96,6 +103,9 @@ func NewFilter(config config, ruleMatcher ruleMatcher, exceptionRuleMatcher rule
if cosmeticRulesInjector == nil {
return nil, errors.New("cosmeticRulesInjector is nil")
}
if cssRulesInjector == nil {
return nil, errors.New("cssRulesInjector is nil")
}
if jsRuleInjector == nil {
return nil, errors.New("jsRuleInjector is nil")
}
Expand All @@ -109,6 +119,7 @@ func NewFilter(config config, ruleMatcher ruleMatcher, exceptionRuleMatcher rule
exceptionRuleMatcher: exceptionRuleMatcher,
scriptletsInjector: scriptletsInjector,
cosmeticRulesInjector: cosmeticRulesInjector,
cssRulesInjector: cssRulesInjector,
jsRuleInjector: jsRuleInjector,
eventsEmitter: eventsEmitter,
}
Expand Down Expand Up @@ -195,6 +206,13 @@ func (f *Filter) AddRule(rule string, filterListName *string, filterListTrusted
}
}

if filterListTrusted && cssrule.RuleRegex.MatchString(rule) {
if err := f.cssRulesInjector.AddRule(rule); err != nil {
return false, fmt.Errorf("add css rule: %w", err)
}
return false, nil
}

if filterListTrusted && jsrule.RuleRegex.MatchString(rule) {
if err := f.jsRuleInjector.AddRule(rule); err != nil {
return false, fmt.Errorf("add js rule: %w", err)
Expand Down Expand Up @@ -268,6 +286,9 @@ func (f *Filter) HandleResponse(req *http.Request, res *http.Response) error {
if err := f.cosmeticRulesInjector.Inject(req, res); err != nil {
log.Printf("error injecting cosmetic rules for %q: %v", logger.Redacted(req.URL), err)
}
if err := f.cssRulesInjector.Inject(req, res); err != nil {
log.Printf("error injecting css rules for %q: %v", logger.Redacted(req.URL), err)
}
if err := f.jsRuleInjector.Inject(req, res); err != nil {
// The error is recoverable, so we log it and continue processing the response.
log.Printf("error injecting js rules for %q: %v", logger.Redacted(req.URL), err)
Expand Down

0 comments on commit cdd2a16

Please sign in to comment.