Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions services/web/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Config struct {
type Asset struct {
CorePath string `yaml:"core_path" env:"WEB_ASSET_CORE_PATH" desc:"Serve OpenCloud Web assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OC_BASE_DATA_PATH/web/assets/core" introductionVersion:"1.0.0"`
ThemesPath string `yaml:"themes_path" env:"OC_ASSET_THEMES_PATH;WEB_ASSET_THEMES_PATH" desc:"Serve OpenCloud themes from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OC_BASE_DATA_PATH/web/assets/themes" introductionVersion:"1.0.0"`
FontsPath string `yaml:"fonts_path" env:"WEB_ASSET_FONTS_PATH" desc:"Serve fonts from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OC_BASE_DATA_PATH/web/assets/fonts" introductionVersion:"%%NEXT%%"`
AppsPath string `yaml:"apps_path" env:"WEB_ASSET_APPS_PATH" desc:"Serve OpenCloud Web apps assets from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OC_BASE_DATA_PATH/web/assets/apps" introductionVersion:"1.0.0"`
}

Expand Down
1 change: 1 addition & 0 deletions services/web/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func DefaultConfig() *config.Config {
CorePath: filepath.Join(defaults.BaseDataPath(), "web/assets/core"),
AppsPath: filepath.Join(defaults.BaseDataPath(), "web/assets/apps"),
ThemesPath: filepath.Join(defaults.BaseDataPath(), "web/assets/themes"),
FontsPath: filepath.Join(defaults.BaseDataPath(), "web/assets/fonts"),
},
GatewayAddress: "eu.opencloud.api.gateway",
Web: config.Web{
Expand Down
5 changes: 5 additions & 0 deletions services/web/pkg/server/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,17 @@ func Server(opts ...Option) (http.Service, error) {
fsx.NewBasePathFs(fsx.NewOsFs(), options.Config.Asset.ThemesPath),
fsx.NewBasePathFs(fsx.FromIOFS(web.Assets), "assets/themes"),
)
fontFS := fsx.NewFallbackFS(
fsx.NewBasePathFs(fsx.NewOsFs(), options.Config.Asset.FontsPath),
fsx.NewBasePathFs(fsx.FromIOFS(web.Assets), "assets/fonts"),
)

handle, err := svc.NewService(
svc.Logger(options.Logger),
svc.CoreFS(coreFS.IOFS()),
svc.AppFS(appsFS.IOFS()),
svc.ThemeFS(themeFS),
svc.FontFS(fontFS.IOFS()),
svc.AppsHTTPEndpoint(_customAppsEndpoint),
svc.Config(options.Config),
svc.GatewaySelector(gatewaySelector),
Expand Down
8 changes: 8 additions & 0 deletions services/web/pkg/service/v0/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Options struct {
AppsHTTPEndpoint string
CoreFS fs.FS
AppFS fs.FS
FontFS fs.FS
ThemeFS *fsx.FallbackFS
}

Expand Down Expand Up @@ -82,6 +83,13 @@ func AppFS(val fs.FS) Option {
}
}

// FontFS provides a function to set the fontFS option.
func FontFS(val fs.FS) Option {
return func(o *Options) {
o.FontFS = val
}
}

// ThemeFS provides a function to set the themeFS option.
func ThemeFS(val *fsx.FallbackFS) Option {
return func(o *Options) {
Expand Down
101 changes: 95 additions & 6 deletions services/web/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"encoding/json"
"io"
"io/fs"
"net/http"
"net/url"
Expand All @@ -14,12 +15,12 @@
"github.com/go-chi/chi/v5"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/riandyrn/otelchi"
"golang.org/x/image/font/sfnt"

"github.com/opencloud-eu/opencloud/pkg/account"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/middleware"
"github.com/opencloud-eu/opencloud/pkg/tracing"
"github.com/opencloud-eu/opencloud/pkg/x/io/fsx"
"github.com/opencloud-eu/opencloud/services/web/pkg/assets"
"github.com/opencloud-eu/opencloud/services/web/pkg/config"
"github.com/opencloud-eu/opencloud/services/web/pkg/theme"
Expand Down Expand Up @@ -54,8 +55,6 @@
logger: options.Logger,
config: options.Config,
mux: m,
coreFS: options.CoreFS,
themeFS: options.ThemeFS,
gatewaySelector: options.GatewaySelector,
}

Expand All @@ -78,6 +77,98 @@
r.Post("/", themeService.LogoUpload)
r.Delete("/", themeService.LogoReset)
})
r.Route("/fonts", func(r chi.Router) {
r.Get("/fonts.json", func(w http.ResponseWriter, r *http.Request) {
fontFiles, err := fs.ReadDir(options.FontFS, ".")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

fontMap := []map[string]any{}
for _, fontFile := range fontFiles {
if fontFile.IsDir() {
continue
}

fLogger := options.Logger.Info().Str("font", fontFile.Name())

uri, err := url.JoinPath(options.Config.Web.Config.Server, svc.config.HTTP.Root, "/fonts", path.Base(fontFile.Name()))
if err != nil {
fLogger.Err(err).Msg("could not build font uri")
continue
}

f, err := options.FontFS.Open(fontFile.Name())
if err != nil {
fLogger.Err(err).Msg("could not open font file")
continue
}

b, err := io.ReadAll(f)
switch {
case err != nil:
fLogger.Err(err).Msg("could not read font file")
continue
case len(b) == 0:
fLogger.Msg("font file is empty")
continue
}

font, err := sfnt.Parse(b)
if err != nil {
fLogger.Err(err).Msg("could not parse font file")
continue
}

buf := new(sfnt.Buffer)
nameID := func(id sfnt.NameID) string {
name, err := font.Name(buf, id)
if err != nil {
fLogger.Err(err).Msg("could not extract font details")
}

return name
}

fontMap = append(fontMap, map[string]any{
"copyright": nameID(sfnt.NameIDCopyright),
"family": nameID(sfnt.NameIDFamily),
"version": nameID(sfnt.NameIDVersion),
"trademark": nameID(sfnt.NameIDTrademark),
"manufacturer": nameID(sfnt.NameIDManufacturer),
"designer": nameID(sfnt.NameIDDesigner),
"description": nameID(sfnt.NameIDDescription),
"vendor_url": nameID(sfnt.NameIDVendorURL),
"designer_url": nameID(sfnt.NameIDDesignerURL),
"license": nameID(sfnt.NameIDLicense),
"license_url": nameID(sfnt.NameIDLicenseURL),
"uri": uri,
})
}

b, err := json.Marshal(map[string]any{
"kind": "fontconfiguration",
"server": "OpenCloud Fonts",
"fonts": fontMap,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

_, err = w.Write(b)

Check failure on line 160 in services/web/pkg/service/v0/service.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

services/web/pkg/service/v0/service.go#L160

Detected directly writing or similar in 'http.ResponseWriter.write()'. This bypasses HTML escaping that prevents cross-site scripting vulnerabilities.
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
r.Mount("/", svc.Static(
options.FontFS,
path.Join(svc.config.HTTP.Root, "/fonts"),
options.Config.HTTP.CacheTTL,
))
})
r.Route("/themes", func(r chi.Router) {
r.Get("/{id}/theme.json", themeService.Get)
r.Mount("/", svc.Static(
Expand All @@ -92,7 +183,7 @@
options.Config.HTTP.CacheTTL,
))
r.Mount("/", svc.Static(
svc.coreFS,
options.CoreFS,
svc.config.HTTP.Root,
options.Config.HTTP.CacheTTL,
))
Expand All @@ -110,8 +201,6 @@
logger log.Logger
config *config.Config
mux *chi.Mux
coreFS fs.FS
themeFS *fsx.FallbackFS
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
}

Expand Down