Added explicit supported URL schemes matching for all players to reduce protocol abuse, added relevant section to README, added tests for new code

This commit is contained in:
Baldomo
2022-12-30 01:32:08 +01:00
parent e2a3cb1833
commit 410a3e8e6d
15 changed files with 483 additions and 346 deletions

View File

@@ -7,3 +7,9 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[{Makefile}]
indent_style = tab
[{*.html,*.js,*.json,*.css}]
indent_size = 2

View File

@@ -1,5 +1,6 @@
SRC:=config.go ipc.go options.go $(wildcard cmd/open-in-mpv/*)
EXT_SRC:=$(wildcard extension/Chrome/*) extension/Firefox/manifest.json
SCRIPTS_DIR:=scripts
all: build/linux.tar build/mac.tar build/windows.tar build/firefox.zip
@@ -9,8 +10,8 @@ builddir:
build/linux/open-in-mpv: $(SRC) builddir
@echo -e "\n# Building for Linux"
env CGO_ENABLED=0 GOOS=linux GOARCh=amd64 go build -ldflags="-s -w" -o $@ ./cmd/open-in-mpv
cp scripts/install-protocol.sh $(dir $@)
cp scripts/open-in-mpv.desktop $(dir $@)
cp $(SCRIPTS_DIR)/install-protocol.sh $(dir $@)
cp $(SCRIPTS_DIR)/open-in-mpv.desktop $(dir $@)
build/linux.tar: build/linux/open-in-mpv
tar cf $@ -C $(dir $@)linux $(notdir $(wildcard build/linux/*))
@@ -22,7 +23,7 @@ build/mac/open-in-mpv.app: $(SRC) scripts/Info.plist builddir
@mkdir -p $@/Contents
env CGO_ENABLED=0 GOOS=darwin GOARCh=amd64 go build -ldflags="-s -w" -o $@/Contents/MacOS/open-in-mpv ./cmd/open-in-mpv
cp config.yml $@/Contents/MacOS/
cp scripts/Info.plist $@/Contents
cp $(SCRIPTS_DIR)/Info.plist $@/Contents
build/mac.tar: build/mac/open-in-mpv.app
tar cf $@ -C $(dir $@)/mac open-in-mpv.app
@@ -30,7 +31,7 @@ build/mac.tar: build/mac/open-in-mpv.app
build/windows/open-in-mpv.exe: $(SRC) builddir
@echo -e "\n# Building for Windows"
env CGO_ENABLED=0 GOOS=windows GOARCh=amd64 go build -ldflags="-s -w -H windowsgui" -o $@ ./cmd/open-in-mpv
cp scripts/install-protocol.reg $(dir $@)
cp $(SCRIPTS_DIR)/install-protocol.reg $(dir $@)
build/windows.tar: build/windows/open-in-mpv.exe
tar cf $@ -C $(dir $@)windows $(notdir $(wildcard build/windows/*))
@@ -45,7 +46,7 @@ install: build/linux/open-in-mpv
cp build/linux/open-in-mpv /usr/bin
install-protocol:
scripts/install-protocol.sh
$(SCRIPTS_DIR)/install-protocol.sh
uninstall:
rm /usr/bin/open-in-mpv

View File

@@ -9,15 +9,14 @@
This is a simple web extension (for Chrome and Firefox) which helps open any video in the currently open tab in the [mpv player](https://mpv.io).
The extension itself shares a lot of code with the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in Go (this is a rewrite from C++).
The extension itself shares a lot of code with the one from the awesome [iina](https://github.com/iina/iina), while the (bare) native binary is written in Go (this is a rewrite from C++).
- [Installation](#installation)
- [Configuration](#configuration)
- [Flag overrides](#flag-overrides)
- [Example](#example)
- [The `mpv://` protocol](#the-mpv-protocol)
- [Playlist and `enqueue` functionality](#playlist-and-enqueue-functionality)
- [Player support](#player-support)
- [Supported protocols](#supported-protocols)
### Installation
> Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases).
@@ -38,23 +37,24 @@ The configuration file has to be named `config.yml` and can be placed in the sam
The configuration file has the following structure:
```yaml
fake: [ open_in_mpv.Player ]
name: [ string ]
executable: [ string ]
fullscreen: [ string ]
pip: [ string ]
enqueue: [ string ]
new_window: [ string ]
needs_ipc: [ true | false ]
flag_overrides: [ map[string]string ]
fake: # open_in_mpv.Player
name: # string
executable: # string
fullscreen: # string
pip: # string
enqueue: # string
new_window: # string
needs_ipc: # true | false
supported_protocols: # []string
flag_overrides: # map[string]string
```
> See [the default configuration](config.yml) as an example
> See [the default configuration](config.yml) or the [example](#example) below
And the `open_in_mpv.Player` object is defined as follows:
| Key | Example value | Description |
|---------------|----------------------------------------|--------------|
| --------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `name` | `mpv` | Full name of the video player; not used internally |
| `executable` | `mpv` | The player's binary path/name (doesn't need full path if already in `$PATH`) |
| `fullscreen` | `"--fs"` | Flag override to open the player in fullscreen (can be empty) |
@@ -62,7 +62,8 @@ And the `open_in_mpv.Player` object is defined as follows:
| `enqueue` | `"--enqueue"` | Flag override to add the video to the player's queue (can be empty) |
| `new_window` | `"--new-window"` | Flag override to force open a new player window with the video (can be empty) |
| `needs_ipc` | `false` | Controls whether the player needs IPC communication (only generates mpv-compatible JSON commands, used for enqueing videos) |
| `flag_overrides` | `"*": "--mpv-options=%s"` | Defines arbitrary text overrides for command line flags (see below) |
| `supported_protocols` | `["http", "https"]` | An arbitrary whitelist of protocols the player supports. See the [relevant section](#supported-protocols) |
| `flag_overrides` | `"*": "--mpv-%s"` | Defines arbitrary text overrides for command line flags (see below) |
#### Flag overrides
@@ -98,6 +99,11 @@ players:
enqueue: "--enqueue"
new_window: ""
needs_ipc: true
supported_protocols:
- http
- https
- ftp
- ftps
flag_overrides:
"*": "--mpv-options=%s"
```
@@ -105,7 +111,7 @@ players:
### The `mpv://` protocol
`open-in-mpv install-protocol` will create a custom `xdg-open` desktop file with a scheme handler for the `mpv://` protocol. This lets `xdg-open` call `open-in-mpv` with an encoded URI, so that it can be parsed and the information can be relayed to `mpv` - this logic follows how `iina` parses and opens custom URIs with the `iina://` protocol on Mac. `install-protocol.sh` has the same functionality.
Please note that this specification is enforced quite strictly, as the program will error out when:
Please note that this specification is enforced quite strictly, as the program will error out when at least one of the following conditions is true:
- The protocol is not `mpv://`
- The method/path is not `/open`
@@ -114,7 +120,7 @@ Please note that this specification is enforced quite strictly, as the program w
The table below is a simple documentation of the URL query keys and values used to let the `open-in-mpv` executable what to do.
| Key | Example value | Description |
|---------------|----------------------------------------|-------------|
| ------------- | -------------------------------------- | ------------------------------------------------------------------------------ |
| `url` | `https%3A%2F%2Fyoutu.be%2FdQw4w9WgXcQ` | The actual file URL to be played, URL-encoded |
| `full_screen` | `1` | Controls whether the video is played in fullscreen mode |
| `pip` | `1` | Simulates a picture-in-picture mode (only works with mpv for now) |
@@ -132,3 +138,6 @@ input-ipc-server=/tmp/mpvsocket
### Player support
Supported players are defined in `config.yml`, where the struct `Player` ([see `config.go`](config.go)) defines supported functionality and command line flag overrides. To request support for a player you're welcome to open a new issue or a pull request or just add your own in your configuration file.
### Supported protocols
Since opening an arbitrary URL with a shell command can cause remote code execution on the host machine (for example by loading arbitrary `.so` files on a player by using special [schemes](https://en.wikipedia.org/wiki/List_of_URI_schemes)), only protocols/[schemes](https://en.wikipedia.org/wiki/List_of_URI_schemes) explicitly specified in the configuration will be processed by the native binary without errors. Defaults to `["http", "https"]` if empty. There is also no special instructions parsing or catch-all values.

View File

@@ -28,6 +28,9 @@ type Player struct {
NewWindow string `yaml:"new_window"`
// Controls whether this player needs IPC command to enqueue videos
NeedsIpc bool `yaml:"needs_ipc"`
// Controls which (video URL) schemes are to be opened by the current
// player. There is no match-all, each protocol has to be manuall specified
SupportedSchemes []string `yaml:"supported_protocols"`
// Overrides for any generic flag
FlagOverrides map[string]string `yaml:"flag_overrides"`
}
@@ -37,6 +40,11 @@ type Config struct {
Players map[string]Player
}
var defaultSupportedSchemas = []string{
"http",
"https",
}
var defaultConfig = Config{
Players: map[string]Player{
"mpv": {
@@ -52,7 +60,8 @@ var defaultConfig = Config{
},
}
// Tries to load configuration file with fallback
// Tries to load configuration file with fallback to a default configuration
// object
func LoadConfig() error {
confDirs := configdir.New("", "open-in-mpv")
confDirs.LocalPath, _ = filepath.Abs(".")
@@ -68,11 +77,25 @@ func LoadConfig() error {
return err
}
return yaml.Unmarshal(data, &defaultConfig)
err = yaml.Unmarshal(data, &defaultConfig)
if err != nil {
return err
}
// If the player has no external configuration, use strict defaults
for name, player := range defaultConfig.Players {
if len(player.SupportedSchemes) == 0 {
log.Printf("Player '%s' has no schemas, setting to defaults", player.Name)
player.SupportedSchemes = defaultSupportedSchemas
defaultConfig.Players[name] = player
}
}
return nil
}
// Returns player information for the given name if present, otherwise nil
func GetPlayerInfo(name string) *Player {
func GetPlayerConfig(name string) *Player {
lowerName := strings.ToLower(name)
if p, ok := defaultConfig.Players[lowerName]; ok {
return &p

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,7 +9,9 @@
<script src="common.js" type="module"></script>
<script src="background.js" type="module"></script>
</head>
<body>
</body>
</html>

View File

@@ -1,39 +1,39 @@
class Option {
constructor(name, type, defaultValue) {
this.name = name;
this.type = type;
this.defaultValue = defaultValue;
this.name = name
this.type = type
this.defaultValue = defaultValue
}
setValue(value) {
switch (this.type) {
case "radio":
Array.prototype.forEach.call(document.getElementsByName(this.name), (el) => {
el.checked = el.value === value;
});
break;
el.checked = el.value === value
})
break
case "checkbox":
document.getElementsByName(this.name).forEach(el => el.checked = value);
break;
document.getElementsByName(this.name).forEach(el => el.checked = value)
break
case "select":
document.getElementsByName(this.name).forEach(el => el.value = value);
break;
document.getElementsByName(this.name).forEach(el => el.value = value)
break
case "text":
document.getElementsByName(this.name).forEach(el => el.value = value);
break;
document.getElementsByName(this.name).forEach(el => el.value = value)
break
}
}
getValue() {
switch (this.type) {
case "radio":
return document.querySelector(`input[name="${this.name}"]:checked`).value;
return document.querySelector(`input[name="${this.name}"]:checked`).value
case "checkbox":
return document.querySelector(`input[name="${this.name}"]`).checked;
return document.querySelector(`input[name="${this.name}"]`).checked
case "select":
return document.querySelector(`select[name="${this.name}"]`).value;
return document.querySelector(`select[name="${this.name}"]`).value
case "text":
return document.querySelector(`input[name="${this.name}"]`).value;
return document.querySelector(`input[name="${this.name}"]`).value
}
}
}
@@ -44,87 +44,92 @@ const _options = [
new Option("mpvPlayer", "select", "mpv"),
new Option("useCustomFlags", "checkbox", false),
new Option("customFlags", "text", "")
];
]
export function getOptions(callback) {
const getDict = {};
_options.forEach((item) => {
getDict[item.name] = item.defaultValue;
const getDict = {}
_options.forEach(item => {
getDict[item.name] = item.defaultValue
})
chrome.storage.sync.get(getDict, callback);
chrome.storage.sync.get(getDict, callback)
}
export function saveOptions() {
const saveDict = {};
_options.forEach((item) => {
saveDict[item.name] = item.getValue();
const saveDict = {}
_options.forEach(item => {
saveDict[item.name] = item.getValue()
})
chrome.storage.sync.set(saveDict);
chrome.storage.sync.set(saveDict)
}
export function restoreOptions() {
getOptions((items) => {
_options.forEach((option) => {
option.setValue(items[option.name]);
});
});
_options.forEach(option => {
option.setValue(items[option.name])
})
})
}
export function openInMPV(tabId, url, options = {}) {
const baseURL = `mpv:///open?`;
const baseURL = `mpv:///open?`
// Encode video URL
const params = [`url=${encodeURIComponent(url)}`];
const params = [`url=${encodeURIComponent(url)}`]
// Add playback options
switch (options.mode) {
case "fullScreen":
params.push("full_screen=1"); break;
params.push("full_screen=1"); break
case "pip":
params.push("pip=1"); break;
params.push("pip=1"); break
case "enqueue":
params.push("enqueue=1"); break;
params.push("enqueue=1"); break
}
// Add new window option
if (options.newWindow) {
params.push("new_window=1");
params.push("new_window=1")
}
// Add alternative player and user-defined custom flags
params.push(`player=${options.mpvPlayer}`);
params.push(`player=${options.mpvPlayer}`)
if (options.useCustomFlags && options.customFlags !== "")
params.push(`flags=${encodeURIComponent(options.customFlags)}`);
params.push(`flags=${encodeURIComponent(options.customFlags)}`)
const code = `
var link = document.createElement('a');
link.href='${baseURL}${params.join("&")}';
document.body.appendChild(link);
link.click();
`;
console.log(code);
chrome.tabs.executeScript(tabId, { code });
var link = document.createElement('a')
link.href='${baseURL}${params.join("&")}'
document.body.appendChild(link)
link.click()`
console.log(code)
chrome.tabs.executeScript(tabId, { code })
}
export function updateBrowserAction() {
getOptions((options) => {
getOptions(options => {
if (options.iconAction === "clickOnly") {
chrome.browserAction.setPopup({ popup: "" });
chrome.browserAction.setPopup({ popup: "" })
chrome.browserAction.onClicked.addListener(() => {
// get active window
// Get active tab
chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => {
if (tabs.length === 0) { return; }
if (tabs.length === 0)
return
// TODO: filter url
const tab = tabs[0];
if (tab.id === chrome.tabs.TAB_ID_NONE) { return; }
const tab = tabs[0]
if (tab.id === chrome.tabs.TAB_ID_NONE)
return
openInMPV(tab.id, tab.url, {
mode: options.iconActionOption,
...options,
});
});
});
} else {
chrome.browserAction.setPopup({ popup: "popup.html" });
})
})
})
return
}
});
chrome.browserAction.setPopup({ popup: "popup.html" })
})
}

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,6 +9,7 @@
<script src="common.js" type="module"></script>
<link rel="stylesheet" href="options.css">
</head>
<body>
<div class="container">
<h2 class="title">Player options</h2>
@@ -41,7 +43,9 @@
<div class="container">
<h2 class="title">Default open action</h2>
<div class="option">
<div class="option-details"><p>Applies to "Open current page in mpv" and the right click context menu</p></div>
<div class="option-details">
<p>Applies to "Open current page in mpv" and the right click context menu</p>
</div>
<div class="item">
<label><input type="radio" name="iconActionOption" value="direct"> Open directly</label>
</div>
@@ -58,4 +62,5 @@
</div>
<script src="options.js" type="module"></script>
</body>
</html>

View File

@@ -1,13 +1,11 @@
import { restoreOptions, saveOptions, updateBrowserAction } from "./common.js";
import { restoreOptions, saveOptions, updateBrowserAction } from "./common.js"
function listener(el) {
el.addEventListener("change", () => {
saveOptions();
updateBrowserAction();
});
}
const addListener = el => el.addEventListener("change", () => {
saveOptions()
updateBrowserAction()
})
document.addEventListener("DOMContentLoaded", restoreOptions);
document.addEventListener("DOMContentLoaded", restoreOptions)
Array.prototype.forEach.call(document.getElementsByTagName("input"), listener);
Array.prototype.forEach.call(document.getElementsByTagName("select"), listener);
Array.prototype.forEach.call(document.getElementsByTagName("input"), addListener)
Array.prototype.forEach.call(document.getElementsByTagName("select"), addListener)

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -10,17 +11,20 @@
font-family: sans-serif;
white-space: nowrap;
}
.menu .menu-item {
padding: 0.5rem;
transition: all 0.1s ease-out;
cursor: pointer;
border-radius: 6px;
}
.menu .menu-item:hover {
background: #e4e4e4;
}
</style>
</head>
<body>
<div class="menu">
<div class="menu-item" id="open-normal">Open in mpv</div>
@@ -31,4 +35,5 @@
</div>
<script src="./popup.js" type="module"></script>
</body>
</html>

View File

@@ -1,20 +1,24 @@
import { openInMPV, getOptions } from "./common.js";
import { openInMPV, getOptions } from "./common.js"
Array.prototype.forEach.call(document.getElementsByClassName("menu-item"), (item) => {
const mode = item.id.split("-")[1];
Array.prototype.forEach.call(document.getElementsByClassName("menu-item"), item => {
const mode = item.id.split("-")[1]
item.addEventListener("click", () => {
getOptions((options) => {
getOptions(options => {
chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => {
if (tabs.length === 0) { return; }
const tab = tabs[0];
if (tab.id === chrome.tabs.TAB_ID_NONE) { return; }
if (tabs.length === 0)
return
const tab = tabs[0]
if (tab.id === chrome.tabs.TAB_ID_NONE)
return
console.log(mode)
openInMPV(tab.id, tab.url, {
mode,
newWindow: mode === "newWindow",
...options,
});
});
});
});
});
})
})
})
})
})

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/Baldomo/open-in-mpv
go 1.15
go 1.18
require (
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0

View File

@@ -11,14 +11,14 @@ import (
// URL and acts as a command generator (both CLI and IPC) to spawn and
// communicate with an mpv player window.
type Options struct {
Flags string
Player string
Url string
Enqueue bool
Flags string
Fullscreen bool
NeedsIpc bool
NewWindow bool
Pip bool
NeedsIpc bool
Player string
Url *url.URL
}
// Utility object to marshal an mpv-compatible JSON command. As defined in the
@@ -30,14 +30,14 @@ type enqueueCmd struct {
// Default constructor for an Option object
func NewOptions() Options {
return Options{
Flags: "",
Player: "mpv",
Url: "",
Enqueue: false,
Flags: "",
Fullscreen: false,
NeedsIpc: false,
NewWindow: false,
Pip: false,
NeedsIpc: false,
Player: "mpv",
Url: nil,
}
}
@@ -60,29 +60,46 @@ func (o *Options) Parse(uri string) error {
return fmt.Errorf("Empty or malformed query: %s", u.RawQuery)
}
playerConfig := GetPlayerConfig(o.Player)
if playerConfig == nil {
return fmt.Errorf("Unsupported player: %s", o.Player)
}
// Extract player command line flags
o.Flags, err = url.QueryUnescape(u.Query().Get("flags"))
if err != nil {
return err
}
o.Url, err = url.QueryUnescape(u.Query().Get("url"))
// Extract video file URL
rawUrl, err := url.QueryUnescape(u.Query().Get("url"))
if err != nil {
return err
}
// Parse the unprocessed URL
o.Url, err = url.Parse(rawUrl)
if err != nil {
return err
}
// Validate the raw URL scheme against the configured ones
if !stringSliceContains(o.Url.Scheme, playerConfig.SupportedSchemes) {
return fmt.Errorf(
"Unsupported schema for player '%s': %s. Did you forget to add it in the configuration?",
playerConfig.Name,
o.Url.Scheme,
)
}
if p, ok := u.Query()["player"]; ok {
o.Player = p[0]
}
if GetPlayerInfo(o.Player) == nil {
return fmt.Errorf("Unsupported player: %s", o.Player)
}
o.Enqueue = u.Query().Get("enqueue") == "1"
o.Fullscreen = u.Query().Get("fullscreen") == "1"
o.NewWindow = u.Query().Get("new_window") == "1"
o.Pip = u.Query().Get("pip") == "1"
o.NeedsIpc = GetPlayerInfo(o.Player).NeedsIpc
o.NeedsIpc = playerConfig.NeedsIpc
return nil
}
@@ -94,52 +111,66 @@ func (o Options) overrideFlags() string {
star bool
)
pInfo := GetPlayerInfo(o.Player)
if pInfo == nil {
playerConfig := GetPlayerConfig(o.Player)
if playerConfig == nil {
return ""
}
_, star = pInfo.FlagOverrides["*"]
// Premature look for star override in configuration
_, star = playerConfig.FlagOverrides["*"]
for _, flag := range strings.Split(o.Flags, " ") {
if star {
// Unconditionally replace all flags with the star template
stripped := strings.TrimLeft(flag, "-")
replaced := strings.ReplaceAll(pInfo.FlagOverrides["*"], `%s`, stripped)
replaced := strings.ReplaceAll(
playerConfig.FlagOverrides["*"],
`%s`,
stripped,
)
ret = append(ret, replaced)
} else {
if override, ok := pInfo.FlagOverrides[flag]; ok {
stripped := strings.TrimLeft(flag, "-")
ret = append(ret, strings.ReplaceAll(override, `%s`, stripped))
continue
}
// Otherwise, iterate over all templates for the current flag and
// do the necessary string replacements
if override, ok := playerConfig.FlagOverrides[flag]; ok {
stripped := strings.TrimLeft(flag, "-")
ret = append(ret, strings.ReplaceAll(
override,
`%s`,
stripped,
))
}
}
return strings.Join(ret, " ")
}
// Builds a CLI command used to invoke the player with the appropriate arguments
// Builds a CLI command used to invoke the player with the appropriate
// arguments
func (o Options) GenerateCommand() []string {
var ret []string
pInfo := GetPlayerInfo(o.Player)
playerConfig := GetPlayerConfig(o.Player)
if o.Fullscreen {
ret = append(ret, pInfo.Fullscreen)
ret = append(ret, playerConfig.Fullscreen)
}
if o.Pip {
ret = append(ret, pInfo.Pip)
ret = append(ret, playerConfig.Pip)
}
if o.Flags != "" {
if len(pInfo.FlagOverrides) == 0 {
if len(playerConfig.FlagOverrides) == 0 {
ret = append(ret, o.Flags)
} else {
ret = append(ret, o.overrideFlags())
}
}
ret = append(ret, o.Url)
ret = append(ret, o.Url.String())
return ret
}
@@ -151,7 +182,7 @@ func (o Options) GenerateIPC() ([]byte, error) {
}
cmd := enqueueCmd{
[]string{"loadfile", o.Url, "append-play"},
[]string{"loadfile", o.Url.String(), "append-play"},
}
ret, err := json.Marshal(cmd)
@@ -165,3 +196,14 @@ func (o Options) GenerateIPC() ([]byte, error) {
return ret, nil
}
// Simple linear search for value in slice of strings
func stringSliceContains(value string, v []string) bool {
for _, elem := range v {
if elem == value {
return true
}
}
return false
}

View File

@@ -1,6 +1,7 @@
package open_in_mpv
import (
"net/url"
"strings"
"testing"
)
@@ -13,6 +14,7 @@ var fakePlayer = Player{
Enqueue: "",
NewWindow: "",
NeedsIpc: true,
SupportedSchemes: []string{"https"},
FlagOverrides: map[string]string{},
}
@@ -25,7 +27,7 @@ func testUrl(query ...string) string {
func Test_GenerateCommand(t *testing.T) {
o := NewOptions()
o.Url = "example.com"
o.Url, _ = url.Parse("example.com")
o.Flags = "--vo=gpu"
o.Pip = true
@@ -79,8 +81,42 @@ func Test_overrideFlags_star(t *testing.T) {
}
func Test_Parse(t *testing.T) {
fakePlayer.FlagOverrides["*"] = "--bar=%s"
defaultConfig.Players["fakeplayer"] = fakePlayer
o := NewOptions()
_ = o.Parse(testUrl("enqueue=1", "pip=1"))
err := o.Parse(testUrl("player=fakeplayer", "enqueue=1", "pip=1"))
if err != nil {
t.Fatal(err)
}
fakePlayer.SupportedSchemes = []string{}
defaultConfig.Players["fakeplayer"] = fakePlayer
err = o.Parse(testUrl("player=fakeplayer", "enqueue=1", "pip=1"))
if err == nil {
t.Logf("%#v", defaultConfig.Players["fakeplayer"])
t.Fatal("Err should not be nil")
}
args := o.GenerateCommand()
t.Logf("%s %v", o.Player, args)
}
func Test_sliceContains(t *testing.T) {
schemas := []string{
"http",
"https",
"ftp",
"ftps",
}
if !stringSliceContains("https", schemas) {
t.Logf("should return true if element (https) is in slice (%v)", schemas)
t.Fail()
}
if stringSliceContains("av", schemas) {
t.Logf("should return false if element (av) is not in slice (%v)", schemas)
t.Fail()
}
}