Major refactor of code: removed dependency on libcurl, added url.hpp, moved everything in the oim namespace, updated readme, added more documentation comments
This commit is contained in:
20
Makefile
20
Makefile
@@ -1,13 +1,17 @@
|
|||||||
INCLUDES = -Isrc/ -I/opt/curl/include
|
INCLUDES = -Isrc/
|
||||||
LDFLAGS = -L/opt/curl/lib
|
CXXFLAGS_debug = -Wall -DDEBUG -g -rdynamic
|
||||||
LDLIBS = -lcurl
|
CXXFLAGS_release = -Wall -fvisibility=hidden -fvisibility-inlines-hidden -std=c++2a -march=x86-64 -mtune=generic -O3 -pipe -fno-plt $(INCLUDES)
|
||||||
CXXFLAGS = -Wall $(INCLUDES) $(LDFLAGS) $(LDLIBS)
|
|
||||||
SRCS = src/curl.hpp \
|
SRCS = src/curl.hpp \
|
||||||
src/mpvopts.hpp \
|
src/mpvopts.hpp \
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
|
||||||
all:
|
all: release firefox
|
||||||
$(CXX) $(CXXFLAGS) -march=x86-64 -mtune=generic -O2 -pipe -fno-plt -o open-in-mpv src/main.cpp
|
|
||||||
|
release:
|
||||||
|
$(CXX) $(CXXFLAGS_release) -o open-in-mpv src/main.cpp
|
||||||
|
|
||||||
|
debug:
|
||||||
|
$(CXX) $(CXXFLAGS_debug) -o open-in-mpv src/main.cpp
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
cp open-in-mpv /usr/bin
|
cp open-in-mpv /usr/bin
|
||||||
@@ -21,4 +25,6 @@ firefox:
|
|||||||
@rm Firefox/{*.html,*.js,*.png,*.css}
|
@rm Firefox/{*.html,*.js,*.png,*.css}
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -f open-in-mpv Firefox.zip Chrome.crx
|
@rm -f open-in-mpv Firefox.zip Chrome.crx
|
||||||
|
|
||||||
|
.PHONY: all release debug install uninstall firefox clean
|
@@ -1,14 +1,13 @@
|
|||||||
# open-in-mpv
|
# 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).
|
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 C++11 (this is a rewrite from Rust).
|
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 C++20 (this is a rewrite from Rust).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
> Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases).
|
> Compiled binaries and packed extensions can be found in the [releases page](https://github.com/Baldomo/open-in-mpv/releases).
|
||||||
|
|
||||||
This project requires [`libcurl`](https://curl.se/libcurl/). Each distro has its own way of installing the library so I will leave that to your favourite web search engine.
|
This project does not require any external library to run or compile release builds. To build and install `open-in-mpv`, just run
|
||||||
|
|
||||||
To build and install `open-in-mpv`, just run
|
|
||||||
```sh
|
```sh
|
||||||
sudo make install
|
sudo make install
|
||||||
```
|
```
|
||||||
|
@@ -10,20 +10,30 @@ using std::string;
|
|||||||
|
|
||||||
const char *DEFAULT_SOCK = "/tmp/mpvsocket";
|
const char *DEFAULT_SOCK = "/tmp/mpvsocket";
|
||||||
|
|
||||||
class mpvipc {
|
namespace oim {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class oim::ipc provides easy communication and basic socket management
|
||||||
|
* for any running mpv instance configured to receive commands over a JSON-IPC server/socket.
|
||||||
|
*/
|
||||||
|
class ipc {
|
||||||
private:
|
private:
|
||||||
sockaddr_un sockaddress;
|
sockaddr_un sockaddress;
|
||||||
int sockfd;
|
int sockfd;
|
||||||
int socklen;
|
int socklen;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
mpvipc() : mpvipc(DEFAULT_SOCK) {};
|
ipc() : ipc(DEFAULT_SOCK) {};
|
||||||
mpvipc(const char *sockpath);
|
ipc(const char *sockpath);
|
||||||
~mpvipc();
|
~ipc();
|
||||||
|
|
||||||
bool send(string cmd);
|
bool send(string cmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
mpvipc::mpvipc(const char *sockpath) {
|
/*
|
||||||
|
* Constructor for oim::ipc
|
||||||
|
*/
|
||||||
|
ipc::ipc(const char *sockpath) {
|
||||||
this->sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
this->sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
this->sockaddress.sun_family = AF_UNIX;
|
this->sockaddress.sun_family = AF_UNIX;
|
||||||
std::strcpy(this->sockaddress.sun_path, sockpath);
|
std::strcpy(this->sockaddress.sun_path, sockpath);
|
||||||
@@ -32,12 +42,20 @@ mpvipc::mpvipc(const char *sockpath) {
|
|||||||
connect(this->sockfd, (const sockaddr*)&this->sockaddress, this->socklen);
|
connect(this->sockfd, (const sockaddr*)&this->sockaddress, this->socklen);
|
||||||
}
|
}
|
||||||
|
|
||||||
mpvipc::~mpvipc() {
|
/*
|
||||||
|
* Destructor for oim::ipc
|
||||||
|
*/
|
||||||
|
ipc::~ipc() {
|
||||||
close(this->sockfd);
|
close(this->sockfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mpvipc::send(string cmd) {
|
/*
|
||||||
|
* Sends a raw command string to the internal socket at DEFAULT_SOCK
|
||||||
|
*/
|
||||||
|
bool ipc::send(string cmd) {
|
||||||
return write(this->sockfd, cmd.c_str(), cmd.length()) != -1;
|
return write(this->sockfd, cmd.c_str(), cmd.length()) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace name
|
||||||
|
|
||||||
#endif
|
#endif
|
@@ -1,5 +1,6 @@
|
|||||||
#include "mpvopts.hpp"
|
#include "options.hpp"
|
||||||
#include "mpvipc.hpp"
|
#include "ipc.hpp"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -43,7 +44,7 @@ int main(int argc, char const *argv[]) {
|
|||||||
return install_protocol();
|
return install_protocol();
|
||||||
}
|
}
|
||||||
|
|
||||||
mpvoptions *mo = new mpvoptions();
|
oim::options *mo = new oim::options();
|
||||||
try {
|
try {
|
||||||
mo->parse(argv[1]);
|
mo->parse(argv[1]);
|
||||||
} catch (string err) {
|
} catch (string err) {
|
||||||
@@ -52,7 +53,7 @@ int main(int argc, char const *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mo->needs_ipc()) {
|
if (mo->needs_ipc()) {
|
||||||
mpvipc *mipc = new mpvipc();
|
oim::ipc *mipc = new oim::ipc();
|
||||||
bool success = mipc->send(mo->build_ipc());
|
bool success = mipc->send(mo->build_ipc());
|
||||||
if (success) {
|
if (success) {
|
||||||
return 0;
|
return 0;
|
||||||
|
180
src/mpvopts.hpp
180
src/mpvopts.hpp
@@ -1,180 +0,0 @@
|
|||||||
#ifndef MPVOPTS_HPP_
|
|
||||||
#define MPVOPTS_HPP_
|
|
||||||
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <memory>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
string url_decode(const string encoded);
|
|
||||||
string query_value(string query, string key);
|
|
||||||
string query_value(string query, string key, string fallback);
|
|
||||||
|
|
||||||
class mpvoptions {
|
|
||||||
private:
|
|
||||||
string url;
|
|
||||||
string flags;
|
|
||||||
string player;
|
|
||||||
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->flags = "";
|
|
||||||
this->player = "mpv";
|
|
||||||
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 << this->player << " ";
|
|
||||||
if (this->fullscreen) ret << "--fs ";
|
|
||||||
if (this->pip) ret << "--ontop --no-border --autofit=384x216 --geometry=98\%:98\% ";
|
|
||||||
if (!this->flags.empty())
|
|
||||||
ret << this->flags << " ";
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
std::cout << ret.str() << std::endl;
|
|
||||||
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->flags = url_decode(query_value(querystr, "flags"));
|
|
||||||
this->player = query_value(querystr, "player", "mpv");
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
string query_value(string query, string key, string fallback) {
|
|
||||||
string ret = query_value(query, key);
|
|
||||||
if (ret.empty()) return fallback;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
125
src/options.hpp
Normal file
125
src/options.hpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#ifndef MPVOPTS_HPP_
|
||||||
|
#define MPVOPTS_HPP_
|
||||||
|
|
||||||
|
#include "url.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace oim {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class oim::options defines a model for the data contained in the mpv://
|
||||||
|
* URL and acts as a command generator (both CLI and IPC) to spawn and
|
||||||
|
* communicate with an mpv player window.
|
||||||
|
*/
|
||||||
|
class options {
|
||||||
|
private:
|
||||||
|
string url;
|
||||||
|
string flags;
|
||||||
|
string player;
|
||||||
|
bool fullscreen;
|
||||||
|
bool pip;
|
||||||
|
bool enqueue;
|
||||||
|
bool new_window;
|
||||||
|
|
||||||
|
public:
|
||||||
|
options();
|
||||||
|
|
||||||
|
string build_cmd();
|
||||||
|
string build_ipc();
|
||||||
|
void parse(const char *url);
|
||||||
|
|
||||||
|
bool needs_ipc();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor for oim::options
|
||||||
|
*/
|
||||||
|
options::options() {
|
||||||
|
this->url = "";
|
||||||
|
this->flags = "";
|
||||||
|
this->player = "mpv";
|
||||||
|
this->fullscreen = false;
|
||||||
|
this->pip = false;
|
||||||
|
this->enqueue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Builds a CLI command used to invoke mpv with the appropriate arguments
|
||||||
|
*/
|
||||||
|
string options::build_cmd() {
|
||||||
|
std::ostringstream ret;
|
||||||
|
|
||||||
|
// TODO: some of these options work only in mpv and not other players
|
||||||
|
// This can be solved by adding a list of some sorts (json/toml/whatever)
|
||||||
|
// containing the flags to use for each functionality and each player
|
||||||
|
ret << this->player << " ";
|
||||||
|
if (this->fullscreen) ret << "--fs ";
|
||||||
|
if (this->pip) ret << "--ontop --no-border --autofit=384x216 --geometry=98\%:98\% ";
|
||||||
|
if (!this->flags.empty())
|
||||||
|
ret << this->flags << " ";
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Builds the IPC command needed to enqueue videos in mpv
|
||||||
|
*/
|
||||||
|
string options::build_ipc() {
|
||||||
|
std::ostringstream ret;
|
||||||
|
|
||||||
|
if (!this->needs_ipc()) return "";
|
||||||
|
|
||||||
|
// TODO: in the future this may need a more 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 options::parse(const char *url_s) {
|
||||||
|
oim::url u(url_s);
|
||||||
|
|
||||||
|
if (u.protocol() != "mpv")
|
||||||
|
throw string("Unsupported protocol supplied: ") + u.protocol();
|
||||||
|
|
||||||
|
if (u.path() != "/open")
|
||||||
|
throw string("Unsupported method supplied: ") + u.path();
|
||||||
|
|
||||||
|
if (u.query().empty())
|
||||||
|
throw string("Empty query");
|
||||||
|
|
||||||
|
this->url = oim::url_decode(u.query_value("url"));
|
||||||
|
this->flags = oim::url_decode(u.query_value("flags"));
|
||||||
|
this->player = u.query_value("player", "mpv");
|
||||||
|
this->fullscreen = u.query_value("fullscreen") == "1";
|
||||||
|
this->pip = u.query_value("pip") == "1";
|
||||||
|
this->enqueue = u.query_value("enqueue") == "1";
|
||||||
|
this->new_window = u.query_value("new_window") == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks wether or not oim::options needs to communicate with mpv via IPC
|
||||||
|
* instead of the command line interface
|
||||||
|
*/
|
||||||
|
bool options::needs_ipc() {
|
||||||
|
// For now this is needed only when queuing videos
|
||||||
|
return this->enqueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace oim
|
||||||
|
|
||||||
|
#endif
|
148
src/url.hpp
Normal file
148
src/url.hpp
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#ifndef MPVURL_HPP_
|
||||||
|
#define MPVURL_HPP_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts a single character to a percent-decodable byte representation
|
||||||
|
* Taken from https://github.com/cpp-netlib/url/blob/main/include/skyr/v1/percent_encoding/percent_decode_range.hpp
|
||||||
|
*/
|
||||||
|
inline std::byte alnum_to_hex(char value) {
|
||||||
|
if ((value >= '0') && (value <= '9')) {
|
||||||
|
return static_cast<std::byte>(value - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((value >= 'a') && (value <= 'f')) {
|
||||||
|
return static_cast<std::byte>(value + '\x0a' - 'a');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((value >= 'A') && (value <= 'F')) {
|
||||||
|
return static_cast<std::byte>(value + '\x0a' - 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<std::byte>(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace oim {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class oim::url contains utility methods to parse a URL string and
|
||||||
|
* access its fields by name (e.g. parses protocol, host, query etc.). Simple
|
||||||
|
* query value searching by key is also provided by url::query_value(string).
|
||||||
|
*/
|
||||||
|
class url {
|
||||||
|
private:
|
||||||
|
string protocol_, host_, path_, query_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Constructor with C-style string URL */
|
||||||
|
url(const char *url_s) : url(string(url_s)) {};
|
||||||
|
|
||||||
|
/* Constructor with C++ std::string URL */
|
||||||
|
url(const string &url_s) {
|
||||||
|
const string prot_end("://");
|
||||||
|
string::const_iterator prot_i = std::search(url_s.begin(), url_s.end(),
|
||||||
|
prot_end.begin(), prot_end.end());
|
||||||
|
protocol_.reserve(std::distance(url_s.begin(), prot_i));
|
||||||
|
std::transform(url_s.begin(), prot_i,
|
||||||
|
std::back_inserter(protocol_),
|
||||||
|
std::ptr_fun<int, int>(tolower)); // protocol is icase
|
||||||
|
if (prot_i == url_s.end())
|
||||||
|
return;
|
||||||
|
std::advance(prot_i, prot_end.length());
|
||||||
|
|
||||||
|
string::const_iterator path_i = std::find(prot_i, url_s.end(), '/');
|
||||||
|
host_.reserve(std::distance(prot_i, path_i));
|
||||||
|
std::transform(prot_i, path_i,
|
||||||
|
std::back_inserter(host_),
|
||||||
|
std::ptr_fun<int, int>(tolower)); // host is icase
|
||||||
|
|
||||||
|
string::const_iterator query_i = std::find(path_i, url_s.end(), '?');
|
||||||
|
path_.assign(path_i, query_i);
|
||||||
|
if (query_i != url_s.end())
|
||||||
|
++query_i;
|
||||||
|
query_.assign(query_i, url_s.end());
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Move constructor for oim::url */
|
||||||
|
url(url &&other) {
|
||||||
|
protocol_ = std::move(other.protocol_);
|
||||||
|
host_ = std::move(other.host_);
|
||||||
|
path_ = std::move(other.path_);
|
||||||
|
query_ = std::move(other.query_);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Accessor for the URL's protocol string */
|
||||||
|
string protocol() { return protocol_; }
|
||||||
|
|
||||||
|
/* Accessor for the URL's host string */
|
||||||
|
string host() { return host_; }
|
||||||
|
|
||||||
|
/* Accessor for the URL's whole path */
|
||||||
|
string path() { return path_; }
|
||||||
|
|
||||||
|
/* Accessor for the URL's whole query string */
|
||||||
|
string query() { return query_; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets a value from a query string given a key
|
||||||
|
*/
|
||||||
|
string query_value(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets a value from a query string given a key (overload with optional fallback if
|
||||||
|
* value isn't found)
|
||||||
|
*/
|
||||||
|
string query_value(string key, string fallback) {
|
||||||
|
string ret = query_value(key);
|
||||||
|
if (ret.empty()) return fallback;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Percent-decodes a URL
|
||||||
|
*/
|
||||||
|
string url_decode(const string encoded) {
|
||||||
|
string ret = "";
|
||||||
|
for (auto i = encoded.begin(); i < encoded.end(); i++) {
|
||||||
|
if (*i == '%') {
|
||||||
|
std::byte b1 = alnum_to_hex(*++i);
|
||||||
|
std::byte b2 = alnum_to_hex(*++i);
|
||||||
|
|
||||||
|
char parsed = static_cast<char>(
|
||||||
|
(0x10u * std::to_integer<unsigned int>(b1)) + std::to_integer<unsigned int>(b2)
|
||||||
|
);
|
||||||
|
ret += parsed;
|
||||||
|
} else {
|
||||||
|
ret += *i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace oim
|
||||||
|
|
||||||
|
#endif
|
Reference in New Issue
Block a user