-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathopen_url.cpp
More file actions
104 lines (92 loc) · 3.07 KB
/
Copy pathopen_url.cpp
File metadata and controls
104 lines (92 loc) · 3.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "open_url.hpp"
#include "utils/encoding.hpp"
#include <algorithm>
#include <cctype>
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# include <shellapi.h>
#else
# include <unistd.h>
#endif
namespace acecode {
namespace {
// 默认平台打开器:POSIX 用 open(macOS) / xdg-open(Linux),fork+execlp 不经
// shell;Windows 用 ShellExecuteW 的 "open" verb(等价于 `start`,但无 cmd
// 转义面)。URL 原样作参数,带空格/引号也安全。
bool platform_open_url(const std::string& url, std::string& error) {
#ifdef _WIN32
const std::wstring wide_url(acecode::utf8_to_wide(url));
HINSTANCE result = ::ShellExecuteW(
nullptr, L"open", wide_url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
const auto code = reinterpret_cast<intptr_t>(result);
if (code > 32) return true;
error = "ShellExecute failed: " + std::to_string(code);
return false;
#else
# ifdef __APPLE__
const char* opener = "open";
# else
const char* opener = "xdg-open";
# endif
const pid_t pid = ::fork();
if (pid < 0) {
error = "fork failed";
return false;
}
if (pid == 0) {
::execlp(opener, opener, url.c_str(), static_cast<char*>(nullptr));
::_exit(127);
}
return true;
#endif
}
} // namespace
bool is_openable_http_url(const std::string& url) {
// 前导空白/空串/控制字符一律拒绝。控制字符(尤其 ESC 0x1B)是终端转义
// 注入通道,必须拦。
if (url.empty() ||
static_cast<unsigned char>(url.front()) < 0x21 ||
static_cast<unsigned char>(url.back()) < 0x21) {
return false;
}
for (const char c : url) {
const unsigned char u = static_cast<unsigned char>(c);
if (u < 0x20 || u == 0x7F) {
return false; // 控制字符 / DEL
}
}
// 仅 http/https(大小写不敏感),且 scheme 后必须有内容(host 至少 1 字符,
// "http://" 裸 scheme 打开无意义)。
const bool http = url.size() > 7 &&
std::equal(url.begin(), url.begin() + 7, "http://",
[](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) == b;
});
const bool https = url.size() > 8 &&
std::equal(url.begin(), url.begin() + 8, "https://",
[](char a, char b) {
return std::tolower(static_cast<unsigned char>(a)) == b;
});
return http || https;
}
OpenUrlResult open_url_in_browser(const std::string& url,
OpenUrlLauncher launcher) {
if (!is_openable_http_url(url)) {
return {false, "URL must be an http/https link"};
}
std::string error;
auto launch = launcher ? std::move(launcher)
: OpenUrlLauncher(platform_open_url);
if (!launch(url, error)) {
if (error.empty()) error = "failed to open URL in browser";
return {false, error};
}
return {true, {}};
}
} // namespace acecode