Total c++ rewrite

This commit is contained in:
Baldomo
2020-07-16 19:27:02 +02:00
parent f43e16dc1f
commit 86d8ea4f75
10 changed files with 296 additions and 495 deletions

167
Cargo.lock generated
View File

@@ -1,167 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "open-in-mpv"
version = "0.1.0"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl"
version = "0.10.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "openssl-sys"
version = "0.9.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "smallvec"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-normalization"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"

View File

@@ -1,15 +0,0 @@
[package]
name = "open-in-mpv"
version = "0.1.0"
authors = ["Leonardo Baldin <leobaldin.2000@gmail.com>"]
edition = "2018"
license = "GPL-3.0-or-later"
build = false
[dependencies]
percent-encoding = "2.1.0"
url = "2.1.1"
[build-dependencies]
byteorder = "1.3.2"
openssl = "0.10.26"

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
INCLUDES = -Isrc/ -I/opt/curl/include
LDFLAGS = -L/opt/curl/lib
LDLIBS = -lcurl
CXXFLAGS = -Wall $(INCLUDES) $(LDFLAGS) $(LDLIBS)
SRCS = src/curl.hpp \
src/mpvopts.hpp \
src/main.cpp
all:
$(CXX) $(CXXFLAGS) -o open-in-mpv src/main.cpp
install: all
cp open-in-mpv /usr/bin
uninstall:
rm /usr/bin/open-in-mpv
clean:
rm open-in-mpv

View File

@@ -1,9 +1,15 @@
# open-in-mpv
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 is a copy of the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in Rust (any stable version will suffice).
The extension itself is a copy of the one from the awesome [iina](https://github.com/iina/iina), while the (bare) backend is written in C++11 (this is a rewrite from Rust).
## Installation
> Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases).
To install `open-in-mpv`, just run
```sh
sudo make install
```
### 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.

View File

@@ -1,85 +0,0 @@
use std::fs::{read, rename, File};
use std::io::Write;
use std::process::Command;
use byteorder::{LittleEndian, WriteBytesExt};
use openssl::error::ErrorStack;
use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, Private};
use openssl::sign::Signer;
type Buffer = Vec<u8>;
// TODO: signing for the Chrome extension
fn main() {
zip_dir("Chrome", "Chrome/ext.zip");
package_crx("./Chrome/ext.zip", "cert.pem");
zip_dir("Firefox", "Firefox/ext.zip");
package_xpi("Firefox/ext.zip");
}
fn zip_dir(path: &str, out: &str) {
Command::new("zip")
.arg("-FS")
.arg(out)
.arg(path)
.output()
.unwrap();
}
fn package_xpi(source: &str) {
let trimmed: &str = source.trim_end_matches(".zip");
rename(source, format!("{}.xpi", trimmed)).unwrap();
}
fn package_crx(source: &str, pem: &str) {
// read input file and store them into buffers
let mut source_buff = read(source).unwrap();
let user_key = load_key_from_file(pem).unwrap();
let mut public_key_content: Vec<u8> = user_key.public_key_to_der().unwrap();
let mut signer = Signer::new(MessageDigest::sha1(), &user_key).unwrap();
signer.update(&source_buff).unwrap();
let mut signature: Vec<u8> = signer.sign_to_vec().unwrap();
let mut pub_key_len: Vec<u8> = vec![];
pub_key_len
.write_u32::<LittleEndian>(public_key_content.len() as u32)
.unwrap();
let mut sig_len: Vec<u8> = vec![];
sig_len
.write_u32::<LittleEndian>(signature.len() as u32)
.unwrap();
let trimmed_filename: &str = source.trim_end_matches(".zip");
let mut package_file: File = File::create(format!("{}.crx", trimmed_filename)).unwrap();
let mut package_buffer: Vec<u8> = Vec::new();
let mut magic_bytes: Buffer = vec![0x43, 0x72, 0x32, 0x34];
let mut version: Buffer = vec![0x02, 0x00, 0x00, 0x00];
package_buffer.append(&mut magic_bytes);
package_buffer.append(&mut version);
package_buffer.append(&mut pub_key_len);
package_buffer.append(&mut sig_len);
package_buffer.append(&mut public_key_content);
package_buffer.append(&mut signature);
package_buffer.append(&mut source_buff);
package_file.write_all(&package_buffer).unwrap();
}
fn load_key_from_file(path: &str) -> Result<PKey<Private>, ErrorStack> {
// Read the private key from the file
let pem_buff = read(path).unwrap();
PKey::private_key_from_pem(&pem_buff)
}

View File

@@ -1,130 +0,0 @@
use std::vec::Vec;
use percent_encoding::percent_decode;
#[derive(Debug)]
pub struct MpvOption {
url: String,
fullscreen: bool,
pip: bool,
enqueue: bool,
}
impl MpvOption {
/// Builds a MpvOption from the supplied arguments.
///
/// # Example
/// ```
/// use open_in_mpv::MpvOption;
/// let mo: MpvOption = MpvOption::new(
/// "url",
/// "1",
/// "0",
/// "0",
/// );
///
/// print!("{:?}", mo);
/// ```
pub fn new(url: &str, fullscreen: &str, pip: &str, enqueue: &str) -> Self {
Self {
url: percent_decode(url.as_bytes())
.decode_utf8()
.unwrap()
.as_ref()
.to_owned(),
fullscreen: match fullscreen {
"1" => true,
"0" => false,
_ => false,
},
pip: match pip {
"1" => true,
"0" => false,
_ => false,
},
enqueue: match enqueue {
"1" => true,
"0" => false,
_ => false,
},
}
}
/// Returns a Vec containing the argument list for a new mpv process
///
/// Each parameter of MpvOption is bound to a set of defaults, e.g. `--ontop --no-border` etc.
///
/// # Example
/// ```
/// use open_in_mpv::MpvOption;
/// let mo: MpvOption = MpvOption::new(
/// "url",
/// "1",
/// "0",
/// "0",
/// );
///
/// print!("{:?}", mo.build_args());
/// ```
pub fn build_args(self) -> Vec<String> {
let mut ret: Vec<String> = Vec::new();
if self.fullscreen {
ret.push("--fs".to_owned());
}
if self.pip {
ret.push("--ontop".to_owned());
ret.push("--no-border".to_owned());
ret.push("--autofit=384x216".to_owned());
ret.push("--geometry=98%:98%".to_owned());
}
if self.enqueue {
// TODO: figure this out
}
ret.push(self.url.to_owned());
return ret;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_option() {
let encoded_url: &str = "https%3A%2F%2Fst3x_plus.cdnfile.info%2Fuser592%2F5543369133e06eedecc4907bfcd0fd45%2FEP.1.360p.mp4%3Ftoken%3DZDpvjlchVpTP0yb_5AsaEw%26expires%3D1562085204%26title%3D(360P%2520-%2520mp4)%2520Terminator%2B4%253A%2BSalvation%2BHD-720p";
let expected_url: String = String::from("https://st3x_plus.cdnfile.info/user592/5543369133e06eedecc4907bfcd0fd45/EP.1.360p.mp4?token=ZDpvjlchVpTP0yb_5AsaEw&expires=1562085204&title=(360P%20-%20mp4)%20Terminator+4%3A+Salvation+HD-720p");
let mo: MpvOption = MpvOption::new(
encoded_url,
"1",
"0",
"1",
);
assert_eq!(mo.url, expected_url);
assert_eq!(mo.fullscreen, true);
assert_eq!(mo.pip, false);
assert_eq!(mo.enqueue, true);
}
#[test]
fn test_build_args() {
let mo: MpvOption = MpvOption {
url: String::from("invalid_url_but_who_cares"),
fullscreen: true,
pip: false,
enqueue: true,
};
let args: Vec<String> = mo.build_args();
assert_eq!(args[0], "--fs".to_owned());
assert_eq!(args[1], "invalid_url_but_who_cares".to_owned());
}
}

64
src/main.cpp Normal file
View File

@@ -0,0 +1,64 @@
#include "mpvopts.hpp"
#include "mpvipc.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
using std::string;
const char *help[2] = {
"This program is not supposed to be called from the command line!",
"Call with 'install-protocol' to instal the xdg-compatible protocol file in ~/.local/share/applications/"
};
bool install_protocol() {
const char *protocol_file = R"([Desktop Entry]
Name=open-in-mpv
Exec=open-in-mpv %u
Type=Application
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/mpv
)";
const char *homedir = std::getenv("HOME");
if (!homedir) return false;
std::ofstream protfile(string(homedir) + "/.local/share/applications/open-in-mpv.desktop");
protfile << protocol_file;
protfile.flush();
protfile.close();
return true;
}
int main(int argc, char const *argv[]) {
if (argc == 1) {
std::cout << help[0] << std::endl << help[1] << std::endl;
return 0;
};
if (string(argv[1]) == "install-protocol") {
return install_protocol();
}
mpvoptions *mo = new mpvoptions();
try {
mo->parse(argv[1]);
} catch (string err) {
std::cout << err << std::endl;
return 1;
}
if (mo->needs_ipc()) {
mpvipc *mipc = new mpvipc();
bool success = mipc->send(mo->build_ipc());
if (!success) {
std::cout << "Error writing to mpv socket" << std::endl;
return 1;
}
} else std::system(mo->build_cmd().c_str());
return 0;
}

View File

@@ -1,97 +0,0 @@
use open_in_mpv::MpvOption;
use std::{env, fs};
use std::collections::HashMap;
use std::process::{Command, exit, Stdio};
use url::Url;
fn main() {
// mpv:///open?url=XXXXXXX&full_screen=1&pip=1&enqueue=0
let raw_arg: String = env::args_os().nth(1).unwrap().into_string().unwrap();
// If first argument is "install-protocol", install the protocol file
if raw_arg == "install-protocol" {
install_protocol();
exit(0);
}
// Parse the String in raw_arg
let parsed_url: Url = match Url::parse(&raw_arg) {
Ok(parsed) => parsed,
Err(e) => {
println!("Error parsing url: {}", raw_arg);
println!("{}", e);
exit(1);
},
};
// Check if url has the correct protocol format
if parsed_url.scheme() != "mpv" {
println!("Unsupported protocol: {}", parsed_url.scheme());
exit(1);
}
// Check for supported methods of operation (just `open` for now)
if parsed_url.path() != "/open" {
println!("Unsupported method: {}", parsed_url.path());
exit(1);
}
// Convert query into a HashMap
let query: HashMap<String, String> = parsed_url.query_pairs().into_owned().collect();
if !query.contains_key("url") {
println!("Url cannot be empty!");
exit(1);
}
// Build a new MpvOption object
// Note: it's now safe to unwrap all the Option's with fallbacks
let mo = MpvOption::new(
query.get("url").unwrap(),
query.get("full_screen")
.as_deref()
.unwrap_or(&"0".to_owned()),
query.get("pip")
.as_deref()
.unwrap_or(&"0".to_owned()),
query.get("enqueue")
.as_deref()
.unwrap_or(&"0".to_owned()),
);
// Spawn a new mpv process
Command::new("mpv")
.args(mo.build_args())
.stdout(Stdio::null())
.spawn()
.expect("failed to open mpv");
exit(0);
}
// Installs the protocol file in "$HOME/.local/share/applications/open-in-mpv.desktop"
fn install_protocol() {
const PROTOCOL_FILE: &str = "[Desktop Entry]
Name=open-in-mpv
Exec=open-in-mpv %u
Type=Application
Terminal=false
NoDisplay=true
MimeType=x-scheme-handler/mpv
";
// Fetch $HOME
let mut file_path: String = String::new();
match env::var("HOME") {
Ok(val) => file_path.push_str(&val),
Err(e) => {
println!("couldn't interpret HOME: {}", e);
return;
},
}
// Refine file path
file_path.push_str("/.local/share/applications/open-in-mpv.desktop");
fs::write(file_path, PROTOCOL_FILE).expect("Error writing file!");
}

43
src/mpvipc.hpp Normal file
View File

@@ -0,0 +1,43 @@
#ifndef MPVIPC_H_
#define MPVIPC_H_
#include <cstring>
#include <unistd.h>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
using std::string;
const char *DEFAULT_SOCK = "/tmp/mpvsocket";
class mpvipc {
private:
sockaddr_un sockaddress;
int sockfd;
int socklen;
public:
mpvipc() : mpvipc(DEFAULT_SOCK) {};
mpvipc(const char *sockpath);
~mpvipc();
bool send(string cmd);
};
mpvipc::mpvipc(const char *sockpath) {
this->sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
this->sockaddress.sun_family = AF_UNIX;
std::strcpy(this->sockaddress.sun_path, sockpath);
this->socklen = sizeof(this->sockaddress);
connect(this->sockfd, (const sockaddr*)&this->sockaddress, this->socklen);
}
mpvipc::~mpvipc() {
close(this->sockfd);
}
bool mpvipc::send(string cmd) {
return write(this->sockfd, cmd.c_str(), cmd.length()) != -1;
}
#endif

163
src/mpvopts.hpp Normal file
View File

@@ -0,0 +1,163 @@
#ifndef MPVOPTS_H_
#define MPVOPTS_H_
#include <curl/curl.h>
#include <cstring>
#include <memory>
#include <sstream>
#include <string>
using std::string;
string url_decode(const string encoded);
string query_value(string query, string key);
class mpvoptions {
private:
string url;
bool fullscreen;
bool pip;
bool enqueue;
bool new_window;
CURLU *curlu;
public:
mpvoptions();
~mpvoptions();
string build_cmd();
string build_ipc();
void parse(const char *url);
bool needs_ipc();
};
mpvoptions::mpvoptions() {
this->curlu = curl_url();
this->fullscreen = false;
this->pip = false;
this->enqueue = false;
}
mpvoptions::~mpvoptions() {
curl_url_cleanup(this->curlu);
}
/*
* Builds a command used to invoke mpv with the appropriate arguments
*/
string mpvoptions::build_cmd() {
std::ostringstream ret;
ret << "mpv ";
if (this->fullscreen) ret << "--fs ";
if (this->pip) ret << "--ontop --no-border --autofit=384x216 --geometry=98\%:98\% ";
// NOTE: this is not needed for mpv (it always opens a new window), maybe for other players?
// if (this->new_window) ret << "--new-window";
ret << this->url;
return ret.str();
}
string mpvoptions::build_ipc() {
std::ostringstream ret;
if (!this->needs_ipc()) return "";
// TODO: in the future this will need a serious json serializer for more complicated commands
// Syntax: {"command": ["loadfile", "%s", "append-play"]}\n
ret << R"({"command": ["loadfile", ")" << this->url << R"(", "append-play"]})" << std::endl;
return ret.str();
}
/*
* Parse a URL and populate the current MpvOptions (uses libcurl for parsing)
*/
void mpvoptions::parse(const char *url) {
curl_url_set(this->curlu, CURLUPART_URL, url, CURLU_NO_DEFAULT_PORT | CURLU_NON_SUPPORT_SCHEME | CURLU_NO_AUTHORITY);
// Check wether the url contains the right scheme or not
char *scheme;
curl_url_get(this->curlu, CURLUPART_SCHEME, &scheme, 0);
if (std::strcmp(scheme, "mpv")) {
curl_free(scheme);
throw string("Unsupported protocol supplied");
}
curl_free(scheme);
// NOTE: libcurl really doesn't like "malformed" url's such as `mpv:///open?xxxxx`
// and it messes up parsing path and host: HOST becomes `open` and PATH becomes `/`
// so we just check HOST instead of PATH for the correct method
char *method;
curl_url_get(this->curlu, CURLUPART_HOST, &method, 0);
if (std::strcmp(method, "open")) {
curl_free(method);
throw string("Unsupported method supplied");
}
curl_free(method);
// Check wether the url query is empty or not
char *query;
curl_url_get(this->curlu, CURLUPART_QUERY, &query, 0);
if (!query) {
curl_free(query);
throw string("Empty query");
}
// If the query is not empty, parse it and populate the current object
string querystr(query);
curl_free(query);
this->url = url_decode(query_value(querystr, "url"));
this->fullscreen = query_value(querystr, "fullscreen") == "1";
this->pip = query_value(querystr, "pip") == "1";
this->enqueue = query_value(querystr, "enqueue") == "1";
this->new_window = query_value(querystr, "new_window") == "1";
}
/*
* Checks wether or not MpvOptions needs to communicate with mpv via IPC
* instead of the command line interface
*/
bool mpvoptions::needs_ipc() {
// For now this is needed only when queuing videos
return this->enqueue;
}
/*
* Percent-decodes a URL using curl_easy_unescape
*/
string url_decode(const string encoded) {
CURL *curl = curl_easy_init();
std::unique_ptr<char, void(*)(char*)> url_decoded(
curl_easy_unescape(
curl,
encoded.c_str(),
(int) encoded.length(),
nullptr
),
[](char *ptr) { curl_free(ptr); }
);
return string(url_decoded.get());
}
/*
* Gets a value from a query string given a key
*/
string query_value(string query, string key) {
// Find the beginning of the last occurrence of `key` in `query`
auto pos = query.rfind(key);
if (pos == string::npos) return "";
// Offset calculation (beginning of the value string associated with `key`):
// pos: positione of the first character of `key`
// key.length(): self explanatory
// 1: length of character '='
int offset = pos + key.length() + 1;
// Return a string starting from the offset and with appropriate length
// (difference between the position of the first '&' char after the value and `offset`)
return query.substr(offset, query.find('&', pos) - offset);
}
#endif