Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First draft implementation for scheme handler registry #405

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ test.json
.idea/
*.iml
.vscode/

# local test deployment-config
deployment-config.json
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Release-Changelog

## TBD (TBD)
### Features
* Add option to register `SchemeHandlers` to deployment-config. See [deployment-config.md](docs/deployment-config.md).

### Changes
* Raise minimum golang requirement to 1.17

Expand Down
3 changes: 1 addition & 2 deletions cmd/bundown/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -145,7 +144,7 @@ func isFolder(filePath string) bool {
}

func mustReaderForFile(filePath string) io.Reader {
data, err := ioutil.ReadFile(filePath)
data, err := os.ReadFile(filePath)
if err != nil {
fatalf("Could not read file \"%s\": %v", filePath, err)
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/echo_field/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/setlog/trivrost/pkg/launcher/config"
Expand Down Expand Up @@ -38,7 +37,7 @@ func parseFlags() {
}

func mustReaderForFile(filePath string) io.Reader {
data, err := ioutil.ReadFile(filePath)
data, err := os.ReadFile(filePath)
if err != nil {
fatalf("Could not read file \"%s\": %v", filePath, err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/installdown/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func configure() *wxsConfig {
}

func mustReaderForFile(filePath string) io.Reader {
data, err := ioutil.ReadFile(filePath)
data, err := os.ReadFile(filePath)
if err != nil {
fatalf("Could not read file \"%s\": %v", filePath, err)
}
Expand Down
90 changes: 85 additions & 5 deletions cmd/launcher/launcher/install.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package launcher

import (
"github.com/setlog/systemuri"
"github.com/setlog/trivrost/cmd/launcher/flags"
"github.com/setlog/trivrost/cmd/launcher/resources"
"github.com/setlog/trivrost/pkg/launcher/config"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"

"github.com/setlog/trivrost/cmd/launcher/flags"
"github.com/setlog/trivrost/cmd/launcher/resources"

"github.com/setlog/trivrost/cmd/launcher/places"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -43,12 +45,12 @@ func IsInstanceInstalled() bool {
return isInstalled
}

// IsInstanceInstalledInSystemMode returns true iff we are in system mode.
// IsInstanceInstalledInSystemMode returns true if we are in system mode.
func IsInstanceInstalledInSystemMode() bool {
return system.FolderExists(places.GetSystemWideBundleFolderPath())
}

// IsInstanceInstalledForCurrentUser returns true iff the launcher's desired path under user files is occupied by the program running this code.
// IsInstanceInstalledForCurrentUser returns true if the launcher's desired path under user files is occupied by the program running this code.
func IsInstanceInstalledForCurrentUser() bool {
programPath := system.GetProgramPath()
targetProgramPath := getTargetProgramPath()
Expand Down Expand Up @@ -111,6 +113,8 @@ func Install(launcherFlags *flags.LauncherFlags) {

InstallShortcuts(targetProgramPath, launcherFlags)

// Registering the scheme handlers had to happen in run.go, since we do not have the deployment config here, yet.

MustRestartWithInstalledBinary(launcherFlags)
}

Expand All @@ -124,6 +128,82 @@ func InstallShortcuts(targetProgramPath string, launcherFlags *flags.LauncherFla
waitGroup.Wait()
}

// RegisterSchemeHandlers registers the schemes defined in the deployment config
func RegisterSchemeHandlers(launcherFlags *flags.LauncherFlags, schemeHandlers []config.SchemeHandler) {
transmittingFlags := launcherFlags.GetTransmittingFlags()
for _, schemeHandler := range schemeHandlers {
// TODO: Create flag "-lockagnostic" (or such) to allow to eliminate all self-restarting behavior (when used in combination with "-skipselfupdate") to reduce UI flickering?
// TODO: Create and then always add flag "-skipschemehandlerregistry" (or such) here to prevent -extra-env from being added?
// TODO: systemuri does not presently implement %%-escapes according to deployment-config.md.
binaryPath := system.GetBinaryPath()
// We want to pass a few flags from the current execution (transmitting flags) as well, but only if they are not set in the passed arguments.
finalArguments := []string{}
finalArguments = removeFromList(transmittingFlags, extractArguments(schemeHandler.Arguments))
finalArguments = filterWhitelistArguments(finalArguments)

transmittingFlagsFiltered := removeFromList(transmittingFlags, finalArguments)
transmittingFlagsFiltered = []string{} // TODO: We actually want to create a whitelist here of flags that are okay to retain

arguments := strings.Join(transmittingFlagsFiltered, " ") + schemeHandler.Arguments
err := systemuri.RegisterURLHandler(resources.LauncherConfig.BrandingName, schemeHandler.Scheme, binaryPath, arguments)
if err != nil {
log.Warnf("Registering the scheme \"%s\" failed: %v", schemeHandler.Scheme, err)
}
}
}

// TODO: Make case insensitive
func filterWhitelistArguments(arguments []string) []string {
// slice of entries to allow in the arguments list
whitelist := []string{
"debug",
"skipselfupdate",
"roaming",
"deployment-config",
"extra-env",
}

var filteredArguments []string

sort.Strings(whitelist)
for _, argument := range arguments {
if found := sort.SearchStrings(whitelist, argument); found < len(whitelist) && whitelist[found] == argument {
continue
}
filteredArguments = append(filteredArguments, argument)
}

return filteredArguments
}

// TODO: Make case insensitive
func removeFromList(sourceList []string, itemsToRemove []string) []string {
argSet := make(map[string]bool)
for _, item := range itemsToRemove {
argSet[item] = true
}

var filteredSourceList []string
for _, item := range sourceList {
if _, ok := argSet[item]; !ok {
filteredSourceList = append(filteredSourceList, item)
}
}
return filteredSourceList
}

func extractArguments(input string) []string {
words := strings.Fields(input)
var args []string
for _, w := range words {
if strings.HasPrefix(w, "-") || strings.HasPrefix(w, "--") {
arg := strings.SplitN(w, "=", 2)[0]
args = append(args, arg)
}
}
return args
}

func MustRestartWithInstalledBinary(launcherFlags *flags.LauncherFlags) {
locking.RestartWithBinary(true, getTargetBinaryPath(), launcherFlags)
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/launcher/launcher/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package launcher

import (
"context"

"github.com/setlog/trivrost/pkg/launcher/config"

"github.com/setlog/trivrost/cmd/launcher/flags"
"github.com/setlog/trivrost/cmd/launcher/gui"
"github.com/setlog/trivrost/cmd/launcher/locking"
"github.com/setlog/trivrost/cmd/launcher/places"
"github.com/setlog/trivrost/cmd/launcher/resources"
"github.com/setlog/trivrost/pkg/launcher/config"

"github.com/setlog/trivrost/pkg/fetching"
"github.com/setlog/trivrost/pkg/logging"
Expand All @@ -24,7 +22,7 @@ func Run(ctx context.Context, launcherFlags *flags.LauncherFlags) {
updater := createUpdater(ctx, wireHandler(gui.NewGuiDownloadProgressHandler(fetching.MaxConcurrentDownloads)))

gui.SetStage(gui.StageGetDeploymentConfig, 0)
updater.Prepare(resources.LauncherConfig.DeploymentConfigURL)
updater.ObtainDeploymentConfig(resources.LauncherConfig.DeploymentConfigURL)

if !IsInstanceInstalledInSystemMode() && !launcherFlags.SkipSelfUpdate {
updateLauncherToLatestVersion(updater, launcherFlags)
Expand All @@ -33,6 +31,8 @@ func Run(ctx context.Context, launcherFlags *flags.LauncherFlags) {

gui.SetStage(gui.StageLaunchApplication, 0)
handleUpdateOmissions(ctx, updater)
// Registering the schema handlers here instead of install.go, since we have the updater (deployment config) here
RegisterSchemeHandlers(launcherFlags, updater.GetDeploymentConfig().SchemeHandlers)
launch(ctx, updater.GetDeploymentConfig().Execution, launcherFlags)
}

Expand Down
11 changes: 11 additions & 0 deletions cmd/launcher/launcher/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"regexp"
"runtime"

"github.com/setlog/systemuri"
"github.com/setlog/trivrost/cmd/launcher/locking"

"github.com/setlog/trivrost/cmd/launcher/flags"
Expand Down Expand Up @@ -38,11 +39,21 @@ func uninstall(launcherFlags *flags.LauncherFlags) {
gui.ShowWaitDialog("Uninstalling "+brandingName, "Please wait as "+brandingName+" is uninstalling.")
deletePlainFiles()
deleteBundles()
unregisterSchemeHandlers()
defer prepareProgramDeletionWithFinalizerFunc()()
gui.HideWaitDialog()
gui.BlockingDialog("Uninstallation complete", brandingName+" has been uninstalled.", []string{"Close"}, 0, launcherFlags.DismissGuiPrompts)
}

// unregisterSchemeHandlers removes all handlers that are associated with this binary
func unregisterSchemeHandlers() {
binaryPath := system.GetBinaryPath()
err := systemuri.UnregisterURLHandlerByPath(binaryPath)
if err != nil {
log.Warnf("Unregistering the schemes for binary \"%s\" failed: %v", binaryPath, err)
}
}

func deletePlainFiles() {
deleteDesktopShortcuts()
if runtime.GOOS != system.OsMac {
Expand Down
5 changes: 2 additions & 3 deletions cmd/launcher/locking/signature_io.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ package locking
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/setlog/trivrost/pkg/system"
log "github.com/sirupsen/logrus"
)

func readProcessSignatureListFile(filePath string) (procSigs []system.ProcessSignature) {
bytes, err := ioutil.ReadFile(filePath)
bytes, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
Expand All @@ -36,7 +35,7 @@ func mustWriteProcessSignatureListFile(filePath string, procSigs []system.Proces
if err != nil {
panic(fmt.Sprintf("Could not marshal process signature slice of length %d: %v", len(procSigs), err))
}
err = ioutil.WriteFile(filePath, bytes, 0666)
err = os.WriteFile(filePath, bytes, 0666)
if err != nil {
panic(fmt.Sprintf("Could not write process signature list file \"%s\": %v", filePath, err))
}
Expand Down
7 changes: 3 additions & 4 deletions cmd/metawriter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -56,7 +55,7 @@ func replacePlaceholders(text string) string {

func mustReadFile(filePath string) string {
fmt.Printf("Metawriter: Reading \"%s\".\n", filePath)
data, err := ioutil.ReadFile(filePath)
data, err := os.ReadFile(filePath)
if err != nil {
fatalf("Could not read \"%s\": %v", filePath, err)
}
Expand All @@ -65,7 +64,7 @@ func mustReadFile(filePath string) string {

func mustWriteFile(filePath string, data []byte) {
fmt.Printf("Metawriter: Writing \"%s\".\n", filePath)
err := ioutil.WriteFile(filePath, data, 0600)
err := os.WriteFile(filePath, data, 0600)
if err != nil {
fatalf("Could not open file \"%s\" for writing: %v", filePath, err)
}
Expand Down Expand Up @@ -108,7 +107,7 @@ func validateVariables() {
}

func mustReaderForFile(filePath string) io.Reader {
data, err := ioutil.ReadFile(filePath)
data, err := os.ReadFile(filePath)
if err != nil {
fatalf("Could not read file \"%s\": %v", filePath, err)
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/signer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
)

Expand All @@ -35,7 +34,8 @@ func createSignatures() {
if err != nil {
fatalf("Creating of a signature for the file %s failed: %v", targetFiles[i], err)
}
if err = ioutil.WriteFile(targetFiles[i]+".signature", []byte(signature), 0644); err != nil {

if err = os.WriteFile(targetFiles[i]+".signature", []byte(signature), 0644); err != nil {
fatalf("Could not write a signature into the file %s.signature: %v", targetFiles[i], err)
}
}
Expand All @@ -55,7 +55,7 @@ func createFileSignature(key *rsa.PrivateKey, fileContent []byte) (string, error
}

func readFile(fileName string) []byte {
content, err := ioutil.ReadFile(fileName)
content, err := os.ReadFile(fileName)
if err != nil {
fatalf("Could not read a file %s: %v", fileName, err)
}
Expand Down
9 changes: 5 additions & 4 deletions cmd/validator/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ package main

import (
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"os"
"time"
)

func getFile(fileUrlString string) ([]byte, error) {
fileUrl, err := url.Parse(fileUrlString)
if err == nil && fileUrl.Scheme == "file" {
fileUrl.Scheme = ""
return ioutil.ReadFile(fileUrl.String())
return os.ReadFile(fileUrl.String())
} else if err != nil || fileUrl.Scheme == "" {
return ioutil.ReadFile(fileUrlString)
return os.ReadFile(fileUrlString)
}

client := &http.Client{}
Expand All @@ -27,7 +28,7 @@ func getFile(fileUrlString string) ([]byte, error) {
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("received bad status code %s", resp.Status)
}
return ioutil.ReadAll(resp.Body)
return io.ReadAll(resp.Body)
}

func getHttpHeadResult(url string) (responseCode int, err error) {
Expand Down
3 changes: 3 additions & 0 deletions docs/deployment-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The deployment-config is a JSON-file which is supposed to be hosted on a webserv
## Fields

* **`Timestamp`** (string): A timestamp in the form `YYYY-MM-DD HH:mm:SS` which indicates when the deployment-config was last changed. This field protects trivrost against attacks. A utility script `script/insert_timestamp` is provided, which replaces a placeholder with a current timestamp. It can be called like this: `insert_timestamp "<TIMESTAMP>" …/deployment-config.json`. See [security.md](security.md) for more information.
* **`SchemeHandlers`** (array): An array of objects which define scheme handlers to be registered with the operating system to run this trivrost app with a specified command line. The idea is to use it in combination with the `--extra-env` commandline option to pass info to the launched application. See also the [exhaustive example](../examples/deployment-config.json.complex.example) of the deployment-config.
* **`Scheme`** (string): The scheme for which to register the handler, such as `mailto`. We recommend to use some sort of reverse-DNS scheme, e.g. `com.example.productname.customername`.
* **`Arguments`** (string): The arguments to pass. Any occurrences of the special string `%s` will be replaced with the operating system-specific template which receives the resource part of the URL (i.e. path, query and fragment). Any occurrences of `%%` will be replaced with `%` to allow for a literal string `%s` to be written (we do not recommend doing so).
* **`LauncherUpdate`** (array): An array of objects which define bundle configurations for how trivrost updates itself. When trivrost runs, this list must boil down to either one single configuration, or no configurations, through filtering by `TargetPlatforms`.
* **`BundleInfoURL`**, **`BaseURL`**, **`TargetPlatforms`**: See [Common fields](#Common-fields) below.
* **`Bundles`** (array): An array of objects which define the bundles which trivrost should download and keep up to date.
Expand Down
3 changes: 3 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ The deployment config tells trivrost where to check for updates to itself and it

## trivrost deployment artifact
We occasionally need to differ between just trivrost's executable binary and its entire deployment artifact as a whole. On Windows and Linux these two concepts are the same thing. On MacOS however, the deployment artifact of trivrost actually is a `.app`-folder, which contains the binary at `Contents/MacOS/launcher`.

## Transmitting flags
Whenever trivrost restarts itself as part of its exclusive lock and self-update mechanisms, it passes most of the command line arguments it was run with (such as `-skipselfupdate`) to the new instance as well. We refer to arguments affected by this as "transmitting flags".
Loading