3
0

Fixes res. translation enc/dec (accents, html)

This commit is contained in:
Denis Arh
2021-10-21 20:49:33 +02:00
parent 4cd54a58f8
commit 556ffc5ec4
9 changed files with 67 additions and 234 deletions

View File

@@ -1,154 +0,0 @@
package service
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// - automation.workflow.yaml
import (
"context"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
intAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/locale"
"github.com/cortezaproject/corteza-server/store"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
)
type (
resourceTranslationsManager struct {
actionlog actionlog.Recorder
locale locale.Resource
store store.Storer
ac localeAccessController
}
localeAccessController interface {
CanManageResourceTranslations(context.Context) bool
}
ResourceTranslationsManagerService interface {
Workflow(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error)
Upsert(context.Context, locale.ResourceTranslationSet) error
Locale() locale.Resource
}
)
var ErrNotAllowedToManageResourceTranslations = errors.Unauthorized("not allowed to manage resource translations")
func ResourceTranslationsManager(ls locale.Resource) *resourceTranslationsManager {
return &resourceTranslationsManager{
actionlog: DefaultActionlog,
store: DefaultStore,
ac: DefaultAccessControl,
locale: ls,
}
}
func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.ResourceTranslationSet) (err error) {
// User is allowed to manage resource translations when:
// - managed resource translation strings are all for default language
// or
// - user is allowed to manage resource translations
if rr.ContainsForeign(svc.Locale().Default().Tag) {
if !svc.ac.CanManageResourceTranslations(ctx) {
return ErrNotAllowedToManageResourceTranslations
}
}
// @todo validation
me := intAuth.GetIdentityFromContext(ctx)
// - group by resource
localeByRes := make(map[string]locale.ResourceTranslationSet)
for _, r := range rr {
localeByRes[r.Resource] = append(localeByRes[r.Resource], r)
}
// - for each resource, fetch the current state
sysLocale := make(systemTypes.ResourceTranslationSet, 0, len(rr))
for res, rr := range localeByRes {
current, _, err := store.SearchResourceTranslations(ctx, svc.store, systemTypes.ResourceTranslationFilter{
Resource: res,
})
if err != nil {
return err
}
// get deltas and prepare upsert accordingly
aux := current.New(rr)
aux.Walk(func(cc *systemTypes.ResourceTranslation) error {
cc.ID = nextID()
cc.CreatedAt = *now()
cc.CreatedBy = me.Identity()
return nil
})
sysLocale = append(sysLocale, aux...)
aux = current.Old(rr)
aux.Walk(func(cc *systemTypes.ResourceTranslation) error {
cc.UpdatedAt = now()
cc.UpdatedBy = me.Identity()
return nil
})
sysLocale = append(sysLocale, aux...)
}
err = store.UpsertResourceTranslation(ctx, svc.store, sysLocale...)
if err != nil {
return err
}
// Reload ALL resource translations
// @todo we could probably do this more selectively and refresh only updated resources?
_ = locale.Global().ReloadResourceTranslations(ctx)
return nil
}
func (svc resourceTranslationsManager) Locale() locale.Resource {
return svc.locale
}
func (svc resourceTranslationsManager) Workflow(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error) {
var (
err error
out locale.ResourceTranslationSet
res *types.Workflow
k types.LocaleKey
)
res, err = svc.loadWorkflow(ctx, svc.store, ID)
if err != nil {
return nil, err
}
for _, tag := range svc.locale.Tags() {
k = types.LocaleKeyWorkflowName
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: k.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path),
})
k = types.LocaleKeyWorkflowDescription
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: k.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path),
})
}
return out, nil
}

View File

@@ -1,12 +0,0 @@
package service
import (
"context"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/store"
)
func (svc resourceTranslationsManager) loadWorkflow(ctx context.Context, s store.Storer, ID uint64) (*types.Workflow, error) {
return loadWorkflow(ctx, s, ID)
}

View File

@@ -72,6 +72,10 @@ func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.Res
}
}
for _, r := range rr {
r.Msg = locale.SanitizeMessage(r.Msg)
}
// @todo validation
me := intAuth.GetIdentityFromContext(ctx)

View File

@@ -120,7 +120,7 @@ var (
// ResourceTranslation returns string representation of Locale resource for ModuleField by calling ModuleFieldResourceTranslation fn
//
// Locale resource is in the compose:module-field/... format
// Locale resource is in "compose:module-field/..." format
//
// This function is auto-generated
func (r ModuleField) ResourceTranslation() string {
@@ -162,7 +162,7 @@ func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyModuleFieldLabel.Path,
Msg: r.Label,
Msg: locale.SanitizeMessage(r.Label),
})
}
@@ -177,7 +177,7 @@ func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) {
// ResourceTranslation returns string representation of Locale resource for Module by calling ModuleResourceTranslation fn
//
// Locale resource is in the compose:module/... format
// Locale resource is in "compose:module/..." format
//
// This function is auto-generated
func (r Module) ResourceTranslation() string {
@@ -216,7 +216,7 @@ func (r *Module) EncodeTranslations() (out locale.ResourceTranslationSet) {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyModuleName.Path,
Msg: r.Name,
Msg: locale.SanitizeMessage(r.Name),
})
}
@@ -227,7 +227,7 @@ func (r *Module) EncodeTranslations() (out locale.ResourceTranslationSet) {
// ResourceTranslation returns string representation of Locale resource for Namespace by calling NamespaceResourceTranslation fn
//
// Locale resource is in the compose:namespace/... format
// Locale resource is in "compose:namespace/..." format
//
// This function is auto-generated
func (r Namespace) ResourceTranslation() string {
@@ -270,21 +270,21 @@ func (r *Namespace) EncodeTranslations() (out locale.ResourceTranslationSet) {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyNamespaceName.Path,
Msg: r.Name,
Msg: locale.SanitizeMessage(r.Name),
})
}
if r.Meta.Subtitle != "" {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyNamespaceSubtitle.Path,
Msg: r.Meta.Subtitle,
Msg: locale.SanitizeMessage(r.Meta.Subtitle),
})
}
if r.Meta.Description != "" {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyNamespaceDescription.Path,
Msg: r.Meta.Description,
Msg: locale.SanitizeMessage(r.Meta.Description),
})
}
@@ -293,7 +293,7 @@ func (r *Namespace) EncodeTranslations() (out locale.ResourceTranslationSet) {
// ResourceTranslation returns string representation of Locale resource for Page by calling PageResourceTranslation fn
//
// Locale resource is in the compose:page/... format
// Locale resource is in "compose:page/..." format
//
// This function is auto-generated
func (r Page) ResourceTranslation() string {
@@ -335,14 +335,14 @@ func (r *Page) EncodeTranslations() (out locale.ResourceTranslationSet) {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyPageTitle.Path,
Msg: r.Title,
Msg: locale.SanitizeMessage(r.Title),
})
}
if r.Description != "" {
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKeyPageDescription.Path,
Msg: r.Description,
Msg: locale.SanitizeMessage(r.Description),
})
}

View File

@@ -67,6 +67,10 @@ func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.Res
}
}
for _, r := range rr {
r.Msg = locale.SanitizeMessage(r.Msg)
}
// @todo validation
me := intAuth.GetIdentityFromContext(ctx)
@@ -209,3 +213,4 @@ func updateTranslations(ctx context.Context, ac localeAccessControl, lsvc Resour
return nil
}

View File

@@ -47,7 +47,7 @@ var (
// ResourceTranslation returns string representation of Locale resource for {{ .Resource }} by calling {{ .Resource }}ResourceTranslation fn
//
// Locale resource is in the {{ .Locale.ResourceType }}/... format
// Locale resource is in "{{ .Locale.ResourceType }}/..." format
//
// This function is auto-generated
func (r {{ .Resource }}) ResourceTranslation() string {
@@ -112,7 +112,7 @@ func (r *{{ .Resource }}) EncodeTranslations() (out locale.ResourceTranslationSe
out = append(out, &locale.ResourceTranslation{
Resource: r.ResourceTranslation(),
Key: LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }}.Path,
Msg: r.{{ .Field }},
Msg: locale.SanitizeMessage(r.{{ .Field }}),
})
}

15
pkg/locale/input.go Normal file
View File

@@ -0,0 +1,15 @@
package locale
import (
"html"
"github.com/microcosm-cc/bluemonday"
)
var (
stripHtml = bluemonday.StripTagsPolicy().Sanitize
)
func SanitizeMessage(in string) string {
return html.UnescapeString(stripHtml(in))
}

27
pkg/locale/input_test.go Normal file
View File

@@ -0,0 +1,27 @@
package locale
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_SanitizeMessage(t *testing.T) {
tests := []struct {
name string
in string
out string
}{
{"simple", "abc", "abc"},
{"accents", "čšž", "čšž"},
{"html", "<b>čšž</b>", "čšž"},
{"broken html 1", "<b>čšž</b", "čšž"},
{"broken html 2", "b>čšž</b", "b>čšž"},
{"broken html 3", "<b fff=\"čšž</b", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.out, SanitizeMessage(tt.in))
})
}
}

View File

@@ -2,10 +2,8 @@ package types
import (
"database/sql/driver"
"html"
"strings"
"time"
"unicode/utf8"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/locale"
@@ -88,7 +86,7 @@ outer:
Lang: Lang{Tag: language.Make(b.Lang)},
Resource: b.Resource,
K: b.Key,
Message: sanitizeHtml(b.Msg),
Message: b.Msg,
})
}
return
@@ -99,7 +97,7 @@ func (set ResourceTranslationSet) Old(bb locale.ResourceTranslationSet) (out Res
for _, a := range set {
// It's not new
if a.Compare(b) {
a.Message = sanitizeHtml(b.Msg)
a.Message = b.Msg
out = append(out, a)
break
}
@@ -114,59 +112,9 @@ func FromLocale(ll locale.ResourceTranslationSet) (out ResourceTranslationSet) {
Lang: Lang{Tag: language.Make(l.Lang)},
Resource: l.Resource,
K: l.Key,
Message: sanitizeHtml(l.Msg),
Message: l.Msg,
})
}
return out
}
// sanitizeHtml Aggressively strips HTML tags from a string.
// It will only keep anything between `>` and `<`.
func sanitizeHtml(s string) string {
const (
htmlTagStart = 60 // Unicode `<`
htmlTagEnd = 62 // Unicode `>`
)
// Unescape string with any html tags
s = html.UnescapeString(s)
// Set up a string builder and allocate enough memory for the new string.
var builder strings.Builder
builder.Grow(len(s) + utf8.UTFMax)
in := false // True if we are inside an HTML tag.
start := 0 // The index of the previous start tag character `<`
end := 0 // The index of the previous end tag character `>`
for i, c := range s {
// If this is the last character, and we are not in an HTML tag, save it.
if (i+1) == len(s) && end >= start {
builder.WriteString(s[end:])
}
// Keep going if the character is not `<` or `>`
if c != htmlTagStart && c != htmlTagEnd {
continue
}
if c == htmlTagStart {
// Only update the start if we are not in a tag.
// This make sure we strip `<<br>` not just `<br>`
if !in {
start = i
}
in = true
// Write the valid string between the close and start of the two tags.
builder.WriteString(s[end:start])
continue
}
// else c == htmlTagEnd
in = false
end = i + 1
}
s = builder.String()
return s
}