#include "alipan.h" #include "http.h" #include "misc.h" #include #include #include #include #include const std::string ALIPAN_PRE_AUTH = "https://auth.aliyundrive.com/v2/oauth/authorize?"; const std::string ALIPAN_CALLBACK = "https://www.aliyundrive.com/sign/callback"; const std::string ALIPAN_CANARY = "X-Canary: client=windows,app=adrive,version=v4.12.0"; bool alipan::checkLogin() { if (!this->signature.empty()) return true; if (!this->accessToken.empty()) { if (this->getSelfuser()) return true; } else if (!this->rToken.empty()) { if (this->refreshToken()) return true; } // 1. 计算设备ID if (this->deviceId.empty()) { uint8_t digest[32]; randomGet(digest, sizeof(digest)); this->deviceId = util::hexEncode(digest, sizeof(digest)); } // 2. Open Browser WebCommonConfig webCfg; WebCommonReply webReply; std::string code; std::string query = http::encode_form({ {"login_type", "custom"}, {"response_type", "code"}, {"redirect_uri", ALIPAN_CALLBACK}, {"client_id", "25dzX3vbYqktVxyX"}, {"sid", std::to_string(time(nullptr))}, {"state", "{\"origin\":\"file://\"}"}, }); std::string url = ALIPAN_PRE_AUTH + query; if (R_FAILED(webConfigShow(&webCfg, &webReply))) return false; webConfigSetJsExtension(&webCfg, true); webConfigSetUserAgentAdditionalString(&webCfg, "aDrive/4.12.0"); webConfigSetCallbackUrl(&webCfg, ALIPAN_CALLBACK.c_str()); webConfigShow(&webCfg, &webReply); size_t lastUrlLen = 0; std::string lastUrl(1024, '\0'); if (R_FAILED(webReplyGetLastUrl(&webReply, lastUrl.data(), lastUrl.size(), &lastUrlLen))) return false; lastUrl.resize(lastUrlLen); int pos = lastUrl.find("?code=") + 6; int end = lastUrl.find_first_of('&', pos); if (pos < end) code = lastUrl.substr(pos, end - pos); printf("code = %s\n", code.c_str()); // 3. GetToken SetSysDeviceNickName nick; json_object *post = json_object_new_object(); json_object_object_add(post, "code", json_object_new_string(query.c_str())); json_object_object_add(post, "loginType", json_object_new_string("normal")); json_object_object_add(post, "deviceId", json_object_new_string(this->deviceId.c_str())); if (R_SUCCEEDED(setsysGetDeviceNickname(&nick))) { json_object_object_add(post, "deviceName", json_object_new_string(nick.nickname)); } json_object_object_add(post, "modelName", json_object_new_string("Windows客户端")); query = json_object_get_string(post); json_object_put(post); json_object *resp = this->request("/token/get", query); if (!resp) return false; json_object *value; if (json_object_object_get_ex(resp, "access_token", &value)) { this->accessToken = json_object_get_string(value); } if (json_object_object_get_ex(resp, "refresh_token", &value)) { this->rToken = json_object_get_string(value); } if (json_object_object_get_ex(resp, "user_id", &value)) { this->userId = json_object_get_string(value); } if (json_object_object_get_ex(resp, "default_drive_id", &value)) { this->driveId = json_object_get_string(value); } json_object_put(post); printf("login user_id(%s) drive_id(%s)\n", this->userId.c_str(), this->driveId.c_str()); return this->createSession(); } void alipan::getListWithParent(const std::string &_parent, std::vector &_out) { json_object *post = json_object_new_object(); json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); json_object_object_add(post, "parent_file_id", json_object_new_string(_parent.c_str())); json_object_object_add(post, "limit", json_object_new_int(20)); json_object_object_add(post, "all", json_object_new_boolean(false)); json_object_object_add(post, "url_expire_sec", json_object_new_int(14400)); json_object_object_add(post, "image_thumbnail_process", json_object_new_string("image/resize,w_256/format,jpeg")); json_object_object_add( post, "image_url_process", json_object_new_string("image/resize,w_1920/format,jpeg/interlace,1")); json_object_object_add(post, "fields", json_object_new_string("*")); json_object_object_add(post, "order_by", json_object_new_string("updated_at")); json_object_object_add(post, "order_direction", json_object_new_string("DESC")); json_object *resp = this->request("/adrive/v3/file/list", json_object_get_string(post)); json_object_put(post); if (!resp) return; json_object *items = nullptr; if (json_object_object_get_ex(resp, "items", &items)) { json_object *value = nullptr; size_t len = json_object_array_length(items); for (size_t i = 0; i < len; i++) { drive::gdItem newItem; json_object *it = json_object_array_get_idx(items, i); if (json_object_object_get_ex(it, "name", &value)) { newItem.name = json_object_get_string(value); } if (json_object_object_get_ex(it, "file_id", &value)) { newItem.id = json_object_get_string(value); } if (json_object_object_get_ex(it, "parent_file_id", &value)) { newItem.parent = json_object_get_string(value); } if (json_object_object_get_ex(it, "size", &value)) { newItem.size = json_object_get_uint64(value); } if (json_object_object_get_ex(it, "type", &value)) { newItem.isDir = !strcmp(json_object_get_string(value), "folder"); } _out.push_back(newItem); } } json_object_put(resp); } bool alipan::createDir(const std::string &_dirName, const std::string &_parent) { json_object *post = json_object_new_object(); json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); json_object_object_add(post, "parent_file_id", json_object_new_string(_parent.c_str())); json_object_object_add(post, "name", json_object_new_string(_dirName.c_str())); json_object_object_add(post, "type", json_object_new_string("folder")); json_object_object_add(post, "check_name_mode", json_object_new_string("refuse")); json_object *resp = this->request("/adrive/v2/file/createWithFolders", json_object_get_string(post)); json_object_put(post); if (!resp) return false; json_object_put(resp); return true; } bool alipan::deleteFile(const std::string &_fileID) { json_object *post = json_object_new_object(); json_object_object_add(post, "drive_id", json_object_new_string(this->driveId.c_str())); json_object_object_add(post, "file_id", json_object_new_string(_fileID.c_str())); json_object *resp = this->request("/v2/recyclebin/trash", json_object_get_string(post)); json_object_put(post); if (!resp) return false; json_object_put(resp); return true; } json_object *alipan::request(const std::string &api, const std::string &data) { http s("Mozilla/5.0 aDrive/4.12.0"); while (true) { std::vector headers = { "Content-Type: application/json", ALIPAN_CANARY, }; if (this->accessToken.size() > 0) { headers.push_back("Authorization: Bearer " + this->accessToken); } if (this->deviceId.size() > 0) { headers.push_back("X-Device-Id: " + this->deviceId); } if (this->signature.size() > 0) { headers.push_back("X-Signature: " + this->signature); } s.set_headers(headers); std::string resp = s.post("https://api.alipan.com" + api, data); json_object *data = json_tokener_parse(resp.c_str()); if (!data) return nullptr; json_object *code = json_object_object_get(data, "code"); if (!code) return data; const char *codeStr = json_object_get_string(code); if (!codeStr) return data; if (strcmp(codeStr, "AccessTokenInvalid") == 0) { this->refreshToken(); } else if (strcmp(codeStr, "DeviceSessionSignatureInvalid") == 0) { this->createSession(); } else { json_object_put(data); return nullptr; } } } bool alipan::getSelfuser() { json_object *resp = this->request("/v2/user/get", "{}"); if (!resp) return false; json_object *value = nullptr; if (json_object_object_get_ex(resp, "user_id", &value)) { this->userId = json_object_get_string(value); } if (json_object_object_get_ex(resp, "default_drive_id", &value)) { this->driveId = json_object_get_string(value); } json_object_put(resp); return this->signature.empty() ? this->createSession() : true; } bool alipan::refreshToken() { http s; s.set_headers({"Content-Type: application/json", "x-requested-with: XMLHttpRequest"}); json_object *post = json_object_new_object(); json_object_object_add(post, "refresh_token", json_object_new_string(this->rToken.c_str())); json_object_object_add(post, "grant_type", json_object_new_string("refresh_token")); std::string body = s.post("https://auth.alipan.com/v2/account/token", json_object_get_string(post)); json_object_put(post); json_object *value = nullptr; json_object *resp = json_tokener_parse(body.c_str()); if (json_object_object_get_ex(resp, "refresh_token", &value)) { this->rToken = json_object_get_string(value); } if (json_object_object_get_ex(resp, "access_token", &value)) { this->accessToken = json_object_get_string(value); } json_object_put(resp); return this->createSession(); } inline int mbd_rand(void *rng_state, unsigned char *output, size_t len) { randomGet(output, len); return 0; } bool alipan::createSession() { std::string msg = "5dde4e1bdf9e4966b387ba58f4b3fdc3:" + this->deviceId + ":" + this->userId + ":0"; 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 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()); std::string pub_key = util::hexEncode(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 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 = util::hexEncode(sigdata.data(), plen * 2 + 1); SetSysDeviceNickName nick; json_object *post = json_object_new_object(); if (R_SUCCEEDED(setsysGetDeviceNickname(&nick))) { json_object_object_add(post, "deviceName", json_object_new_string(nick.nickname)); } json_object_object_add(post, "modelName", json_object_new_string("Windows客户端")); json_object_object_add(post, "pubKey", json_object_new_string(pub_key.c_str())); json_object *resp = this->request("/users/v1/users/device/create_session", json_object_get_string(post)); json_object_put(post); if (!resp) return false; json_object *success; bool result = false; if (json_object_object_get_ex(resp, "success", &success)) { result = json_object_get_boolean(success); } json_object_put(resp); printf("create session %d pubkey %s\n", result, pub_key.c_str()); return result; }