add alipan
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
|||||||
*.exe
|
*.exe
|
||||||
/build
|
/build
|
||||||
/cDrive.*
|
/cDrive.*
|
||||||
/.vscode/settings.json
|
/.vscode/*
|
||||||
|
.alipan.json
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -9,9 +9,9 @@ CPPFILES := $(foreach dir,$(SRCS),$(notdir $(wildcard $(dir)/*.cpp)))
|
|||||||
OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o)
|
OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o)
|
||||||
|
|
||||||
INCLUDES := -I$(CURDIR)/include -I$(CURDIR)
|
INCLUDES := -I$(CURDIR)/include -I$(CURDIR)
|
||||||
CFLAGS := -g -Wall $(INCLUDES) `curl-config --cflags`
|
CFLAGS := -s -Wall $(INCLUDES) `curl-config --cflags`
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -std=c++17
|
CXXFLAGS := $(CFLAGS) -fno-rtti -std=c++17
|
||||||
LDFLAGS := `curl-config --libs` -lmbedcrypto
|
LDFLAGS := `curl-config --libs` -lmbedcrypto -lfmt
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
@@ -23,4 +23,4 @@ run: all
|
|||||||
@./$(TARGET)
|
@./$(TARGET)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RM) -fr $(BUILD) $(TARGET)
|
$(RM) $(OFILES) $(TARGET)
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
# clist
|
# cDrive
|
||||||
|
|
||||||
Cloud drive explorer for C/C++
|
Cloud drive explorer for C/C++
|
||||||
|
|
||||||
## Build On Debian
|
## Build On Debian
|
||||||
|
|
||||||
* Debian `apt-get install libcurl4-openssl-dev libspdlog-dev libqrcodegen-dev libmbedtls-dev`
|
* Debian `apt-get install libcurl4-openssl-dev libfmt-dev libqrcodegen-dev libmbedtls-dev`
|
||||||
|
|
||||||
## Build On Windows
|
## Build On Windows
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://github.com/gabime/spdlog/archive/v1.13.0.tar.gz | tar zxf -
|
curl -sL https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz | tar zxf -
|
||||||
cmake -B build -G 'MinGW Makefiles' -DCMAKE_INSTALL_PREFIX=/c/MinGW64 -DCMAKE_BUILD_TYPE=Release \
|
cmake -B build -G 'MinGW Makefiles' -DCMAKE_INSTALL_PREFIX=/c/MinGW64 -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DSPDLOG_BUILD_EXAMPLE=OFF -DSPDLOG_BUILD_SHARED=ON
|
-DFMT_DOC=OFF -DFMT_TEST=OFF
|
||||||
|
|
||||||
curl -sL https://github.com/Mbed-TLS/mbedtls/archive/v2.28.3.tar.gz | tar zxf -
|
curl -sL https://github.com/Mbed-TLS/mbedtls/archive/v2.28.3.tar.gz | tar zxf -
|
||||||
cmake -B build -G 'MinGW Makefiles' -DCMAKE_INSTALL_PREFIX=/c/MinGW64 -DCMAKE_BUILD_TYPE=Release \
|
cmake -B build -G 'MinGW Makefiles' -DCMAKE_INSTALL_PREFIX=/c/MinGW64 -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ public:
|
|||||||
typedef std::shared_ptr<drive> ref;
|
typedef std::shared_ptr<drive> ref;
|
||||||
typedef void (*fnQrcode)(const std::string&);
|
typedef void (*fnQrcode)(const std::string&);
|
||||||
|
|
||||||
virtual int qrLogin(fnQrcode printQr) = 0;
|
virtual bool qrLogin(fnQrcode printQr) = 0;
|
||||||
virtual std::vector<dItem> list(const std::string& file_id) = 0;
|
virtual std::vector<dItem> list(const std::string& file_id) = 0;
|
||||||
|
virtual std::string link(const std::string& file_id) = 0;
|
||||||
|
virtual void remove(const std::string& file_id) = 0;
|
||||||
virtual std::string mkdir(const std::string& parent_id, const std::string& name) = 0;
|
virtual std::string mkdir(const std::string& parent_id, const std::string& name) = 0;
|
||||||
virtual std::string upload(const std::string& parent_id, const std::string& file) = 0;
|
virtual std::string upload(const std::string& parent_id, const std::string& file) = 0;
|
||||||
};
|
};
|
||||||
@@ -32,4 +34,4 @@ public:
|
|||||||
drive::ref new_drive(drive_type type);
|
drive::ref new_drive(drive_type type);
|
||||||
|
|
||||||
std::string hex_encode(const unsigned char* data, size_t len);
|
std::string hex_encode(const unsigned char* data, size_t len);
|
||||||
|
std::string device_name();
|
||||||
|
|||||||
24765
include/nlohmann/json.hpp
Normal file
24765
include/nlohmann/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
346
src/drive/alipan.cpp
Normal file
346
src/drive/alipan.cpp
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
#include "alipan.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <mbedtls/ecdsa.h>
|
||||||
|
#include <mbedtls/sha256.h>
|
||||||
|
#include <mbedtls/base64.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const std::string ALIPAN_GET_QRCODE =
|
||||||
|
"https://passport.alipan.com/newlogin/qrcode/generate.do?appName=aliyun_drive&fromSite=52";
|
||||||
|
const std::string ALIPAN_CHECK_QRCODE =
|
||||||
|
"https://passport.alipan.com/newlogin/qrcode/query.do?appName=aliyun_drive&fromSite=52";
|
||||||
|
const std::string ALIPAN_PRE_AUTH =
|
||||||
|
"https://auth.alipan.com/v2/oauth/authorize?login_type=custom&response_type=code"
|
||||||
|
"&redirect_uri=https%3A%2F%2Fwww.aliyundrive.com%2Fsign%2Fcallback&client_id=25dzX3vbYqktVxyX"
|
||||||
|
"&sid=ls2z86hbnehde&state=%7B%22origin%22%3A%22file%3A%2F%2F%22%7D";
|
||||||
|
const std::string ALIPAN_MINI_LOGIN =
|
||||||
|
"https://passport.alipan.com/mini_login.htm?lang=zh_cn&appName=aliyun_drive&appEntrance=web"
|
||||||
|
"&styleType=auto&bizParams=¬LoadSsoView=false¬KeepLogin=false&isMobile=false";
|
||||||
|
const std::string ALIPAN_CANARY = "X-Canary: client=windows,app=adrive,version=v4.12.0";
|
||||||
|
|
||||||
|
bool alipan::qrLogin(fnQrcode printQr) {
|
||||||
|
try {
|
||||||
|
std::ifstream f(".alipan.json");
|
||||||
|
if (f.is_open()) {
|
||||||
|
json j = json::parse(f);
|
||||||
|
this->access_token = j.at("access_token");
|
||||||
|
this->refresh_token = j.at("refresh_token");
|
||||||
|
this->device_id = j.at("device_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->access_token.empty()) {
|
||||||
|
return this->getSelfuser();
|
||||||
|
} else if (!this->refresh_token.empty()) {
|
||||||
|
return this->refreshToken();
|
||||||
|
}
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
fmt::print("load token {}\n", ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTP s;
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
s.set_headers({
|
||||||
|
"Origin: https://passport.alipan.com",
|
||||||
|
"Referer: " + ALIPAN_MINI_LOGIN,
|
||||||
|
ALIPAN_CANARY,
|
||||||
|
});
|
||||||
|
s.get(ALIPAN_GET_QRCODE, &ss);
|
||||||
|
json r = json::parse(ss.str());
|
||||||
|
json qr = r.at("content").at("data");
|
||||||
|
if (qr == nullptr || !qr.contains("codeContent")) return -1;
|
||||||
|
printQr(qr.at("codeContent"));
|
||||||
|
|
||||||
|
std::string query = HTTP::encode_form({
|
||||||
|
{"t", std::to_string(qr.at("t").get<time_t>())},
|
||||||
|
{"ck", qr.at("ck")},
|
||||||
|
{"appName", "aliyun_drive"},
|
||||||
|
{"appEntrance", "web"},
|
||||||
|
{"isMobile", "false"},
|
||||||
|
{"lang", "zh_CN"},
|
||||||
|
{"returnUrl", ""},
|
||||||
|
{"fromSite", "52"},
|
||||||
|
{"bizParams", ""},
|
||||||
|
});
|
||||||
|
|
||||||
|
s.set_headers({
|
||||||
|
"Content-Type: application/x-www-form-urlencoded",
|
||||||
|
"Origin: https://passport.alipan.com",
|
||||||
|
"Referer: " + ALIPAN_MINI_LOGIN,
|
||||||
|
ALIPAN_CANARY,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool scaned = false;
|
||||||
|
while (true) {
|
||||||
|
json j = json::parse(s.post(ALIPAN_CHECK_QRCODE, query));
|
||||||
|
json data = j.at("content").at("data");
|
||||||
|
std::string qrCodeStatus = data.at("qrCodeStatus");
|
||||||
|
|
||||||
|
if (qrCodeStatus == "NEW") {
|
||||||
|
} else if (qrCodeStatus == "SCANED") {
|
||||||
|
if (!scaned) {
|
||||||
|
fmt::print("扫描成功 请在APP上确认\n");
|
||||||
|
scaned = true;
|
||||||
|
}
|
||||||
|
} else if (qrCodeStatus == "EXPIRED") {
|
||||||
|
fmt::print("二维码过期\n");
|
||||||
|
return false;
|
||||||
|
} else if (qrCodeStatus == "CANCELED") {
|
||||||
|
fmt::print("已取消\n");
|
||||||
|
return false;
|
||||||
|
} else if (qrCodeStatus == "CONFIRMED") {
|
||||||
|
// decode token
|
||||||
|
std::string bizExt = data.at("bizExt");
|
||||||
|
std::vector<char> gbkBiz(bizExt.size());
|
||||||
|
size_t out_len = 0;
|
||||||
|
|
||||||
|
mbedtls_base64_decode(
|
||||||
|
(uint8_t*)gbkBiz.data(), gbkBiz.size(), &out_len, (const uint8_t*)bizExt.c_str(), bizExt.size());
|
||||||
|
|
||||||
|
static std::regex remove_re("[^\\x20-\\x7f]");
|
||||||
|
bizExt = std::regex_replace(std::string(gbkBiz.data(), out_len), remove_re, "");
|
||||||
|
if (bizExt.empty()) return -1;
|
||||||
|
|
||||||
|
json login = json::parse(bizExt).at("pds_login_result");
|
||||||
|
this->user_id = login.at("userId");
|
||||||
|
this->drive_id = login.at("defaultDriveId");
|
||||||
|
this->access_token = login.at("accessToken");
|
||||||
|
this->refresh_token = login.at("refreshToken");
|
||||||
|
// 计算设备ID
|
||||||
|
unsigned char digest[32];
|
||||||
|
mbedtls_sha256_ret((uint8_t*)this->user_id.c_str(), this->user_id.size(), digest, 0);
|
||||||
|
this->device_id = hex_encode(digest, sizeof(digest));
|
||||||
|
fmt::print("login user_id({}) drive_id({})\n", this->user_id, this->drive_id);
|
||||||
|
|
||||||
|
return this->createSession();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<dItem> alipan::list(const std::string& file_id) {
|
||||||
|
json data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{"parent_file_id", file_id},
|
||||||
|
{"limit", 20},
|
||||||
|
{"all", false},
|
||||||
|
{"url_expire_sec", 14400},
|
||||||
|
{"image_thumbnail_process", "image/resize,w_256/format,jpeg"},
|
||||||
|
{"image_url_process", "image/resize,w_1920/format,jpeg/interlace,1"},
|
||||||
|
{"video_thumbnail_process", "video/snapshot,t_1000,f_jpg,ar_auto,w_256"},
|
||||||
|
{"fields", "file_id,name,type,"},
|
||||||
|
{"order_by", "updated_at"},
|
||||||
|
{"order_direction", "DESC"},
|
||||||
|
};
|
||||||
|
json j = this->request("/adrive/v3/file/list", data);
|
||||||
|
|
||||||
|
std::vector<dItem> list;
|
||||||
|
for (auto& item : j.at("items")) {
|
||||||
|
dItem it;
|
||||||
|
it.folder = item.at("type") == "folder";
|
||||||
|
it.id = item.at("file_id");
|
||||||
|
it.name = item.at("name");
|
||||||
|
list.push_back(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::print("{}\n", j.dump(2));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string alipan::link(const std::string& file_id) {
|
||||||
|
json data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{"file_id", file_id},
|
||||||
|
{"expire_sec", 14400},
|
||||||
|
};
|
||||||
|
json j = this->request("/v2/file/get_download_url", data);
|
||||||
|
return j.at("url");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string alipan::mkdir(const std::string& parent_id, const std::string& name) {
|
||||||
|
json data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{"parent_file_id", parent_id},
|
||||||
|
{"name", name},
|
||||||
|
{"type", "folder"},
|
||||||
|
{"check_name_mode", "refuse"},
|
||||||
|
};
|
||||||
|
json j = this->request("/adrive/v2/file/createWithFolders", data);
|
||||||
|
return j.at("file_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
void alipan::remove(const std::string& file_id) {
|
||||||
|
json data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{"file_id", file_id},
|
||||||
|
};
|
||||||
|
this->request("/v2/recyclebin/trash", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string alipan::upload(const std::string& parent_id, const std::string& file) {
|
||||||
|
fs::path name(file);
|
||||||
|
json data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{
|
||||||
|
"part_info_list",
|
||||||
|
{
|
||||||
|
{{"part_number", 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"parent_file_id", parent_id},
|
||||||
|
{"name", name.filename().string()},
|
||||||
|
{"size", fs::file_size(file)},
|
||||||
|
{"type", "file"},
|
||||||
|
{"check_name_mode", "overwrite"},
|
||||||
|
{"create_scene", "file_upload"},
|
||||||
|
{"device_name", device_name()},
|
||||||
|
{"content_hash_name", "none"},
|
||||||
|
{"proof_version", "v1"},
|
||||||
|
};
|
||||||
|
json j = this->request("/adrive/v2/file/createWithFolders", data);
|
||||||
|
|
||||||
|
std::ifstream f(file, std::ios::binary);
|
||||||
|
HTTP c;
|
||||||
|
for (auto& info : j.at("part_info_list")) {
|
||||||
|
std::string resp = c.put(info.at("upload_url"), &f);
|
||||||
|
if (!resp.empty()) fmt::print("upload part: {}\n", resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
{"drive_id", this->drive_id},
|
||||||
|
{"file_id", j.at("file_id")},
|
||||||
|
{"upload_id", j.at("upload_id")},
|
||||||
|
};
|
||||||
|
j = this->request("/v2/file/complete", data);
|
||||||
|
return j.at("file_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool alipan::getSelfuser() {
|
||||||
|
json j = this->request("/v2/user/get", {});
|
||||||
|
this->user_id = j.at("user_id");
|
||||||
|
this->drive_id = j.at("default_drive_id");
|
||||||
|
return this->createSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
json alipan::request(const std::string& api, const json& data) {
|
||||||
|
HTTP s;
|
||||||
|
while (true) {
|
||||||
|
std::vector<std::string> headers = {
|
||||||
|
"Content-Type: application/json",
|
||||||
|
ALIPAN_CANARY,
|
||||||
|
};
|
||||||
|
if (this->access_token.size() > 0) {
|
||||||
|
headers.push_back("Authorization: Bearer " + this->access_token);
|
||||||
|
}
|
||||||
|
if (this->device_id.size() > 0) {
|
||||||
|
headers.push_back("X-Device-Id: " + this->device_id);
|
||||||
|
}
|
||||||
|
if (this->signature.size() > 0) {
|
||||||
|
headers.push_back("X-Signature: " + this->signature);
|
||||||
|
}
|
||||||
|
s.set_headers(headers);
|
||||||
|
json j = json::parse(s.post("https://api.alipan.com" + api, data.dump()));
|
||||||
|
|
||||||
|
if (!j.contains("code")) return j;
|
||||||
|
if (j.at("code").is_null()) return j;
|
||||||
|
|
||||||
|
std::string code = j.at("code");
|
||||||
|
if (code == "AccessTokenInvalid") {
|
||||||
|
if (this->refresh_token.empty()) throw std::runtime_error(j.at("message"));
|
||||||
|
this->refreshToken();
|
||||||
|
} else if (code == "DeviceSessionSignatureInvalid") {
|
||||||
|
this->createSession();
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error(j.at("message"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool alipan::refreshToken() {
|
||||||
|
HTTP s;
|
||||||
|
s.set_headers({"Content-Type: application/json", "x-requested-with: XMLHttpRequest"});
|
||||||
|
json data = {{"refresh_token", this->refresh_token}, {"grant_type", "refresh_token"}};
|
||||||
|
std::string r = s.post("https://auth.alipan.com/v2/account/token", data.dump());
|
||||||
|
json j = json::parse(r);
|
||||||
|
if (j.contains("code")) {
|
||||||
|
fmt::print("refresh failed: {}\n", j.dump());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->refresh_token = j.at("refresh_token");
|
||||||
|
this->access_token = j.at("access_token");
|
||||||
|
|
||||||
|
fmt::print("refress success ({})\n", this->refresh_token);
|
||||||
|
return this->createSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int mbd_rand(void* rng_state, unsigned char* output, size_t len) {
|
||||||
|
srand(time(nullptr));
|
||||||
|
for (size_t i = 0; i < len; ++i) output[i] = rand();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool alipan::createSession() {
|
||||||
|
std::string msg = "5dde4e1bdf9e4966b387ba58f4b3fdc3:" + this->device_id + ":" + this->user_id + ":0";
|
||||||
|
std::string pub_key;
|
||||||
|
|
||||||
|
mbedtls_ecdsa_context ctx_sign;
|
||||||
|
mbedtls_ecdsa_init(&ctx_sign);
|
||||||
|
// gen secp256k1 keypair
|
||||||
|
mbedtls_ecdsa_genkey(&ctx_sign, MBEDTLS_ECP_DP_SECP256K1, mbd_rand, nullptr);
|
||||||
|
// mbedtls_ecp_group_load(&ctx_sign.grp, MBEDTLS_ECP_DP_SECP256K1);
|
||||||
|
// mbedtls_mpi_read_string(&ctx_sign.d, 16, this->device_id.c_str());
|
||||||
|
// mbedtls_ecp_mul(&ctx_sign.grp, &ctx_sign.Q, &ctx_sign.d, &ctx_sign.grp.G, mbd_rand, nullptr);
|
||||||
|
|
||||||
|
// dump public key
|
||||||
|
size_t pub_len = 0;
|
||||||
|
std::vector<uint8_t> pub(MBEDTLS_ECP_MAX_BYTES, 0);
|
||||||
|
mbedtls_ecp_point_write_binary(
|
||||||
|
&ctx_sign.grp, &ctx_sign.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &pub_len, pub.data(), pub.size());
|
||||||
|
pub_key = hex_encode(pub.data(), pub_len);
|
||||||
|
|
||||||
|
// sign message
|
||||||
|
unsigned char msg_hash[32];
|
||||||
|
mbedtls_sha256_ret((uint8_t*)msg.c_str(), msg.size(), msg_hash, 0);
|
||||||
|
|
||||||
|
mbedtls_mpi r, s;
|
||||||
|
std::vector<uint8_t> sigdata(MBEDTLS_ECDSA_MAX_LEN, 0);
|
||||||
|
mbedtls_mpi_init(&r);
|
||||||
|
mbedtls_mpi_init(&s);
|
||||||
|
mbedtls_ecdsa_sign(&ctx_sign.grp, &r, &s, &ctx_sign.d, msg_hash, sizeof(msg_hash), mbd_rand, nullptr);
|
||||||
|
|
||||||
|
size_t plen = mbedtls_mpi_size(&r);
|
||||||
|
mbedtls_mpi_write_binary(&r, sigdata.data(), plen);
|
||||||
|
mbedtls_mpi_write_binary(&s, sigdata.data() + plen, plen);
|
||||||
|
sigdata[plen * 2] = 1;
|
||||||
|
mbedtls_mpi_free(&r);
|
||||||
|
mbedtls_mpi_free(&s);
|
||||||
|
mbedtls_ecdsa_free(&ctx_sign);
|
||||||
|
this->signature = hex_encode(sigdata.data(), plen * 2 + 1);
|
||||||
|
// log_debug("sign ({}), pubkey ({}), msg ({})", this->signature, pub_key, msg);
|
||||||
|
|
||||||
|
json data = {
|
||||||
|
{"deviceName", device_name()},
|
||||||
|
{"modelName", "Windows客户端"},
|
||||||
|
{"pubKey", pub_key},
|
||||||
|
};
|
||||||
|
json j = this->request("/users/v1/users/device/create_session", data);
|
||||||
|
|
||||||
|
std::ofstream(".alipan.json") << json({
|
||||||
|
{"access_token", this->access_token},
|
||||||
|
{"refresh_token", this->refresh_token},
|
||||||
|
{"device_id", this->device_id},
|
||||||
|
});
|
||||||
|
return j.at("success").get<bool>();
|
||||||
|
}
|
||||||
31
src/drive/alipan.h
Normal file
31
src/drive/alipan.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include "http.h"
|
||||||
|
#include "drive.h"
|
||||||
|
|
||||||
|
class alipan : public drive {
|
||||||
|
public:
|
||||||
|
bool qrLogin(fnQrcode printQr) override;
|
||||||
|
std::vector<dItem> list(const std::string& file_id) override;
|
||||||
|
std::string link(const std::string& file_id) override;
|
||||||
|
std::string mkdir(const std::string& parent_id, const std::string& name) override;
|
||||||
|
void remove(const std::string& file_id) override;
|
||||||
|
std::string upload(const std::string& parent_id, const std::string& file) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool getSelfuser();
|
||||||
|
bool refreshToken();
|
||||||
|
bool createSession();
|
||||||
|
|
||||||
|
nlohmann::json request(const std::string& api, const nlohmann::json& j);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string device_id;
|
||||||
|
std::string signature;
|
||||||
|
std::string access_token;
|
||||||
|
std::string refresh_token;
|
||||||
|
|
||||||
|
std::string drive_id;
|
||||||
|
std::string user_id;
|
||||||
|
};
|
||||||
18
src/drive/drive.cpp
Normal file
18
src/drive/drive.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "alipan.h"
|
||||||
|
|
||||||
|
static std::map<drive_type, drive::ref> m;
|
||||||
|
|
||||||
|
drive::ref new_drive(drive_type type) {
|
||||||
|
auto it = m.find(type);
|
||||||
|
if (it == m.end()) {
|
||||||
|
switch (type) {
|
||||||
|
case dt_alipan:
|
||||||
|
it->second = std::make_shared<alipan>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("unsupport drive type");
|
||||||
|
}
|
||||||
|
m.insert(std::make_pair(type, it->second));
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
69
src/drive/misc.cpp
Normal file
69
src/drive/misc.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include "drive.h"
|
||||||
|
#include <locale>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <SystemConfiguration/SystemConfiguration.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string hex_encode(const unsigned char* data, size_t len) {
|
||||||
|
std::stringstream ss;
|
||||||
|
for (size_t i = 0; i < len; i++) ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i];
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ansi_to_utf8(const char* locale, std::string const& str_gbk) {
|
||||||
|
std::vector<wchar_t> buff(str_gbk.size());
|
||||||
|
std::locale loc(locale);
|
||||||
|
wchar_t* to_next = nullptr;
|
||||||
|
const char* from_next = nullptr;
|
||||||
|
mbstate_t state = {};
|
||||||
|
int res = std::use_facet<std::codecvt<wchar_t, char, mbstate_t>>(loc).in(state, str_gbk.data(),
|
||||||
|
str_gbk.data() + str_gbk.size(), from_next, buff.data(), buff.data() + buff.size(), to_next);
|
||||||
|
|
||||||
|
if (std::codecvt_base::ok != res) return "";
|
||||||
|
|
||||||
|
std::wstring_convert<std::codecvt_utf8<wchar_t>> cutf8;
|
||||||
|
return cutf8.to_bytes(std::wstring(buff.data(), to_next));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string device_name() {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
SetSysDeviceNickName nick;
|
||||||
|
if (R_SUCCEEDED(setsysGetDeviceNickname(&nick))) {
|
||||||
|
return nick.nickname;
|
||||||
|
}
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
DWORD nSize = 128;
|
||||||
|
std::vector<WCHAR> buf(nSize);
|
||||||
|
if (GetComputerNameW(buf.data(), &nSize)) {
|
||||||
|
std::string name;
|
||||||
|
name.resize(nSize);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, buf.data(), nSize, name.data(), name.size(), nullptr, nullptr);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
CFStringRef nameRef = SCDynamicStoreCopyComputerName(nullptr, nullptr);
|
||||||
|
if (nameRef) {
|
||||||
|
std::vector<char> name(CFStringGetLength(nameRef) * 3);
|
||||||
|
CFStringGetCString(nameRef, name.data(), name.size(), kCFStringEncodingUTF8);
|
||||||
|
CFRelease(nameRef);
|
||||||
|
return name.data();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::vector<char> buf(128);
|
||||||
|
if (gethostname(buf.data(), buf.size()) == 0) {
|
||||||
|
return buf.data();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return "clist";
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ std::string HTTP::encode_form(const Form& form) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int HTTP::get(const std::string& url, std::ostream* out) {
|
int HTTP::get(const std::string& url, std::ostream* out) {
|
||||||
|
out->clear();
|
||||||
curl_easy_setopt(this->easy, CURLOPT_URL, url.c_str());
|
curl_easy_setopt(this->easy, CURLOPT_URL, url.c_str());
|
||||||
curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L);
|
curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L);
|
||||||
return this->perform(out);
|
return this->perform(out);
|
||||||
|
|||||||
50
src/main.cpp
50
src/main.cpp
@@ -1,5 +1,7 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
#include "drive.h"
|
||||||
#include <qrcodegen/qrcodegen.h>
|
#include <qrcodegen/qrcodegen.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
void printQr(const std::string& text) {
|
void printQr(const std::string& text) {
|
||||||
int border = 1;
|
int border = 1;
|
||||||
@@ -10,16 +12,58 @@ void printQr(const std::string& text) {
|
|||||||
|
|
||||||
for (int y = -border; y < width + border; y++) {
|
for (int y = -border; y < width + border; y++) {
|
||||||
for (int x = -border; x < width + border; x++) {
|
for (int x = -border; x < width + border; x++) {
|
||||||
qrcodegen_getModule(qrcode.data(), x, y) ? printf("\033[40m \033[0m") : printf("\033[47m \033[0m");
|
qrcodegen_getModule(qrcode.data(), x, y) ? fmt::print("\033[40m \033[0m")
|
||||||
|
: fmt::print("\033[47m \033[0m");
|
||||||
}
|
}
|
||||||
printf("\n");
|
fmt::print("\n");
|
||||||
}
|
}
|
||||||
printf("\n");
|
fmt::print("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
|
||||||
|
drive::ref c = new_drive(dt_alipan);
|
||||||
|
if (!c->qrLogin(printQr)) return 1;
|
||||||
|
|
||||||
|
uint32_t choice = 0;
|
||||||
|
std::vector<std::string> stack = {"root"};
|
||||||
|
while (true) {
|
||||||
|
auto list = c->list(stack.back());
|
||||||
|
if (stack.size() > 1) {
|
||||||
|
fmt::print("0: 返回上一级\n");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < list.size(); i++) {
|
||||||
|
fmt::print("{}: {}\n", i + 1, list[i].name);
|
||||||
|
}
|
||||||
|
fmt::print("{}: 创建目录\n", list.size() + 1);
|
||||||
|
fmt::print("{}: 上传文件\n", list.size() + 2);
|
||||||
|
fmt::print("选择文件或目录: \n");
|
||||||
|
scanf("%u", &choice);
|
||||||
|
|
||||||
|
if (choice > list.size() + 2 || choice < 0) {
|
||||||
|
break;
|
||||||
|
} else if (choice == 0 && stack.size() > 1) {
|
||||||
|
stack.pop_back();
|
||||||
|
} else if (choice == list.size() + 1) {
|
||||||
|
char name[1024];
|
||||||
|
fmt::print("请输入目录名: \n");
|
||||||
|
scanf("%s", name);
|
||||||
|
c->mkdir(stack.back(), name);
|
||||||
|
} else if (choice == list.size() + 2) {
|
||||||
|
char name[1024];
|
||||||
|
fmt::print("请输入文件名: \n");
|
||||||
|
scanf("%s", name);
|
||||||
|
c->upload(stack.back(), name);
|
||||||
|
} else if (list[choice - 1].folder) {
|
||||||
|
stack.push_back(list[choice - 1].id);
|
||||||
|
} else {
|
||||||
|
std::string link = c->link(list[choice - 1].id);
|
||||||
|
fmt::print("url: {}\n", link);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
109
test/sign.cpp
Normal file
109
test/sign.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// g++ -o sign sign.cpp -l mbedcrypto -lsecp256k1
|
||||||
|
|
||||||
|
#include <mbedtls/ecdsa.h>
|
||||||
|
#include <mbedtls/sha256.h>
|
||||||
|
#include <secp256k1.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
std::vector<uint8_t> hex_decode(const std::string &s) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
for (size_t i = 0; i < s.length(); i += 2) {
|
||||||
|
const std::string hex = s.substr(i, 2);
|
||||||
|
const uint8_t decimal = std::strtol(hex.c_str(), 0, 16);
|
||||||
|
bytes.push_back(decimal);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string hex_encode(const unsigned char *data, size_t len) {
|
||||||
|
std::stringstream ss;
|
||||||
|
for (size_t i = 0; i < len; i++) ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i];
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int mbd_rand(void *rng_state, unsigned char *output, size_t len) {
|
||||||
|
for (size_t i = 0; i < len; ++i) output[i] = rand();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::string device_id = "cbf97fb4ff5c592ab387fd303ac9069b75930cf6e10d58da67f8426dd0f82ff2";
|
||||||
|
std::string user_id = "71b9ac79010a448b95836a2be0dde671";
|
||||||
|
std::string msg = "5dde4e1bdf9e4966b387ba58f4b3fdc3:" + device_id + ":" + user_id + ":0";
|
||||||
|
|
||||||
|
std::vector<uint8_t> pub(MBEDTLS_ECP_MAX_BYTES, 0);
|
||||||
|
std::vector<uint8_t> sigdata(65, 0);
|
||||||
|
|
||||||
|
unsigned char msg_hash[32];
|
||||||
|
int ret = 0;
|
||||||
|
mbedtls_sha256_ret((uint8_t *)msg.c_str(), msg.size(), msg_hash, 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
mbedtls_ecdsa_context ctx_sign;
|
||||||
|
mbedtls_ecdsa_init(&ctx_sign);
|
||||||
|
// load private key
|
||||||
|
mbedtls_ecp_group_load(&ctx_sign.grp, MBEDTLS_ECP_DP_SECP256K1);
|
||||||
|
mbedtls_mpi_read_string(&ctx_sign.d, 16, device_id.c_str());
|
||||||
|
mbedtls_ecp_mul(&ctx_sign.grp, &ctx_sign.Q, &ctx_sign.d, &ctx_sign.grp.G, nullptr, nullptr);
|
||||||
|
// dump public key
|
||||||
|
size_t pub_len = 0;
|
||||||
|
mbedtls_ecp_point_write_binary(&ctx_sign.grp, &ctx_sign.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &pub_len, pub.data(), pub.size());
|
||||||
|
pub.resize(pub_len);
|
||||||
|
printf("pubkey %s\n", hex_encode(pub.data(), pub_len).c_str());
|
||||||
|
// sign message
|
||||||
|
mbedtls_mpi r, s;
|
||||||
|
mbedtls_mpi_init(&r);
|
||||||
|
mbedtls_mpi_init(&s);
|
||||||
|
mbedtls_ecdsa_sign(&ctx_sign.grp, &r, &s, &ctx_sign.d, msg_hash, sizeof(msg_hash), mbd_rand, nullptr);
|
||||||
|
mbedtls_mpi_write_binary(&r, &sigdata[0], 32);
|
||||||
|
mbedtls_mpi_write_binary(&s, &sigdata[32], 32);
|
||||||
|
sigdata[64] = 1;
|
||||||
|
mbedtls_mpi_free(&r);
|
||||||
|
mbedtls_mpi_free(&s);
|
||||||
|
printf("sigdata %s\n", hex_encode(sigdata.data(), sigdata.size()).c_str());
|
||||||
|
mbedtls_ecdsa_free(&ctx_sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
mbedtls_ecdsa_context ctx_verify;
|
||||||
|
mbedtls_ecdsa_init(&ctx_verify);
|
||||||
|
// load public key
|
||||||
|
mbedtls_ecp_group_load(&ctx_verify.grp, MBEDTLS_ECP_DP_SECP256K1);
|
||||||
|
ret = mbedtls_ecp_point_read_binary(&ctx_verify.grp, &ctx_verify.Q, pub.data(), pub.size());
|
||||||
|
if (ret < 0) printf("mbedtls_ecp_point_read_string -0x%x\n", -ret);
|
||||||
|
// verify signature
|
||||||
|
mbedtls_mpi r, s;
|
||||||
|
mbedtls_mpi_init(&r);
|
||||||
|
mbedtls_mpi_init(&s);
|
||||||
|
mbedtls_mpi_read_binary(&r, &sigdata[0], 32);
|
||||||
|
mbedtls_mpi_read_binary(&s, &sigdata[32], 32);
|
||||||
|
ret = mbedtls_ecdsa_verify(&ctx_verify.grp, msg_hash, sizeof(msg_hash), &ctx_verify.Q, &r, &s);
|
||||||
|
if (ret < 0)
|
||||||
|
printf("mbedtls_ecdsa_verify -0x%x\n", -ret);
|
||||||
|
else
|
||||||
|
printf("mbedtls_ecdsa_verify ok\n");
|
||||||
|
mbedtls_mpi_free(&r);
|
||||||
|
mbedtls_mpi_free(&s);
|
||||||
|
mbedtls_ecdsa_free(&ctx_verify);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
secp256k1_ecdsa_signature sig;
|
||||||
|
secp256k1_pubkey pubkey;
|
||||||
|
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||||
|
ret = secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sigdata.data());
|
||||||
|
if (!ret) printf("secp256k1_ecdsa_signature_parse_compact failed\n");
|
||||||
|
ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pub.data(), pub.size());
|
||||||
|
if (!ret) printf("secp256k1_ec_pubkey_parse failed\n");
|
||||||
|
ret = secp256k1_ecdsa_verify(ctx, &sig, msg_hash, &pubkey);
|
||||||
|
if (!ret)
|
||||||
|
printf("secp256k1_ecdsa_verify failed\n");
|
||||||
|
else
|
||||||
|
printf("secp256k1_ecdsa_verify ok\n");
|
||||||
|
secp256k1_context_destroy(ctx);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user