Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
167 lines
5.1 KiB
Go
167 lines
5.1 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"slices"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/app/oembed"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// To update the list of oEmbed providers that we support:
|
|
// 1. Download the latest providers.json file from https://oembed.com/providers.json and place it in this folder
|
|
// 2. If desired, update supportedProviders below to add the names of additional oEmbed providers that we want to use
|
|
// 3. Run `go generate ./channels/app/oembed` from the server folder
|
|
|
|
var (
|
|
// supportedProviders contains the names of all of the oEmbed providers that we currently support.
|
|
//
|
|
// As of writing, we're only going to support YouTube because they've stopped giving us the required OpenGraph
|
|
// metadata. When we want to support oEmbed embeds for other providers, this will need to be updated.
|
|
supportedProviders = []string{
|
|
"YouTube",
|
|
}
|
|
|
|
outputTemplate = template.Must(template.New("providers.go.tmpl").ParseFiles("./generator/providers.go.tmpl"))
|
|
)
|
|
|
|
type oEmbedProvider struct {
|
|
ProviderName string `json:"provider_name"`
|
|
ProviderURL string `json:"provider_url"`
|
|
Endpoints []*oEmbedEndpoint `json:"endpoints"`
|
|
}
|
|
|
|
type oEmbedEndpoint struct {
|
|
Schemes []string `json:"schemes,omitempty"`
|
|
URL string `json:"url"`
|
|
Discovery bool `json:"discovery,omitempty"`
|
|
Formats []string `json:"formats,omitempty"`
|
|
}
|
|
|
|
func main() {
|
|
inputJson, err := os.ReadFile("./generator/providers.json")
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "Unable to read providers.json. Did you forget to put it next to providers_generator.go?"))
|
|
}
|
|
|
|
outputFile, err := os.Create("./providers_gen.go")
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "Unable to open output file"))
|
|
}
|
|
defer outputFile.Close()
|
|
|
|
var input []*oEmbedProvider
|
|
err = json.Unmarshal(inputJson, &input)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "Unable to read providers.json"))
|
|
}
|
|
|
|
var endpoints []*oembed.ProviderEndpoint
|
|
for _, inputProvider := range input {
|
|
if !slices.Contains(supportedProviders, inputProvider.ProviderName) {
|
|
continue
|
|
}
|
|
|
|
providerEndpoints, extractErr := extractEndpointsFromProvider(inputProvider)
|
|
if extractErr != nil {
|
|
panic(errors.Wrap(extractErr, "Unable to convert oEmbedProvider from providers.json to a ProviderEndpoint"))
|
|
}
|
|
endpoints = append(endpoints, providerEndpoints...)
|
|
}
|
|
|
|
err = outputTemplate.Execute(outputFile, map[string]any{
|
|
"Endpoints": endpoints,
|
|
})
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "Unable to write file using template"))
|
|
}
|
|
}
|
|
|
|
// extractEndpointsFromProvider turns the data for one provider into providers.json into multiple, more compact ProviderEndpoints
|
|
func extractEndpointsFromProvider(in *oEmbedProvider) ([]*oembed.ProviderEndpoint, error) {
|
|
var out []*oembed.ProviderEndpoint
|
|
|
|
for _, endpoint := range in.Endpoints {
|
|
// Ensure that the endpoint URL is valid so that we don't need to error check it at runtime
|
|
_, err := url.Parse(endpoint.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var patterns []*regexp.Regexp
|
|
for _, scheme := range endpoint.Schemes {
|
|
pattern, err := schemeToPattern(scheme)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
|
|
if len(patterns) > 0 {
|
|
out = append(out, &oembed.ProviderEndpoint{
|
|
URL: endpoint.URL,
|
|
Patterns: patterns,
|
|
})
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func schemeToPattern(scheme string) (*regexp.Regexp, error) {
|
|
partsPattern := regexp.MustCompile(`^(\w+:(?://)?)([^/]*)(/[^?]*)?(\?[^?]*)?$`)
|
|
parts := partsPattern.FindStringSubmatch(scheme)
|
|
if parts == nil {
|
|
return nil, fmt.Errorf("unable to split scheme %s into parts", scheme)
|
|
} else if len(parts) != 5 {
|
|
return nil, fmt.Errorf("wrong number of parts for scheme %s", scheme)
|
|
}
|
|
|
|
protocol := parts[1]
|
|
if protocol != "http://" && protocol != "https://" && protocol != "spotify:" {
|
|
return nil, fmt.Errorf("unrecognized protocol %s for scheme %s", protocol, scheme)
|
|
}
|
|
domain := parts[2]
|
|
if domain == "" {
|
|
return nil, fmt.Errorf("no domain found for scheme %s", scheme)
|
|
}
|
|
path := parts[3]
|
|
if path == "" && protocol != "spotify:" {
|
|
return nil, fmt.Errorf("no path found for scheme %s", scheme)
|
|
}
|
|
query := parts[4]
|
|
|
|
// Replace any valid wildcards with a temporary character so that we can escape any regexp special characters
|
|
domain = strings.Replace(domain, "*", "%", -1)
|
|
path = strings.Replace(path, "*", "%", -1)
|
|
query = strings.Replace(query, "*", "%", -1)
|
|
|
|
// Escape any other special characters
|
|
protocol = regexp.QuoteMeta(protocol)
|
|
domain = regexp.QuoteMeta(domain)
|
|
path = regexp.QuoteMeta(path)
|
|
query = regexp.QuoteMeta(query)
|
|
|
|
// Replace the temporary character with the proper regexp to match a wildcard in that part of the URL
|
|
domain = strings.Replace(domain, "%", "[^/]*?", -1)
|
|
path = strings.Replace(path, "%", ".*?", -1)
|
|
query = strings.Replace(query, "%", ".*?", -1)
|
|
|
|
// Allow http schemes to match https URLs as well
|
|
if protocol == "http://" {
|
|
protocol = "https?://"
|
|
}
|
|
|
|
return regexp.Compile("^" + protocol + domain + path + query + "$")
|
|
}
|