├── .DS_Store ├── .gitignore ├── Docker-compose.yml ├── LICENSE ├── README.md ├── README_cn.md ├── RoboFile.php ├── api ├── .DS_Store ├── .gitignore ├── app.js ├── package.json ├── pnpm-lock.yaml ├── utils │ └── logger.js └── yarn.lock ├── design └── logo.xd ├── docker ├── Dockerfile ├── app.js └── package.json ├── examples ├── decrypt.py ├── fixediv │ ├── .gitignore │ ├── README.md │ ├── go │ │ ├── README.md │ │ ├── decrypt.go │ │ └── go.mod │ ├── java-simple │ │ ├── DecryptSimple.java │ │ └── README.md │ ├── java │ │ ├── README.md │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── cookiecloud │ │ │ └── decrypt │ │ │ └── DecryptMain.java │ ├── nodejs │ │ ├── README.md │ │ ├── decrypt.js │ │ └── package.json │ ├── php │ │ ├── README.md │ │ ├── composer.json │ │ ├── decrypt.php │ │ └── src │ │ │ └── Decrypt.php │ ├── python │ │ ├── README.md │ │ ├── decrypt.py │ │ └── requirements.txt │ └── test_all.sh └── playwright │ ├── .gitignore │ ├── package.json │ ├── playwright.config.js │ ├── tests │ ├── config.example.js │ └── example.spec.js │ └── yarn.lock ├── ext ├── .gitignore ├── assets │ └── typescript.svg ├── components │ └── counter.ts ├── entrypoints │ ├── background.ts │ ├── content.ts │ └── popup │ │ ├── App.tsx │ │ ├── index.html │ │ ├── main.tsx │ │ └── style.css ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public │ ├── _locales │ │ ├── en │ │ │ └── messages.json │ │ └── zh_CN │ │ │ └── messages.json │ └── icon │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ ├── 96.png │ │ └── icon.png ├── tailwind.config.js ├── tsconfig.json ├── utils │ ├── functions.ts │ └── messaging.ts └── wxt.config.ts ├── extension ├── .github │ └── workflows │ │ └── submit.yml ├── .gitignore ├── .prettierrc.cjs ├── .vscode │ └── settings.json ├── README.md ├── assets │ └── icon.png ├── background │ ├── index.ts │ └── messages │ │ └── config.ts ├── content.ts ├── function.js ├── locales │ ├── en │ │ └── messages.json │ └── zh_CN │ │ └── messages.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── popup.html ├── popup.tsx ├── postcss.config.js ├── style.scss ├── tailwind.config.js └── tsconfig.json ├── images ├── 20230121092535.png ├── 20230121095327.png └── 20230121141854.png └── web ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── icon.png └── vite.svg ├── src ├── App.css ├── App.jsx ├── assets │ └── react.svg ├── index.css └── main.jsx ├── tailwind.config.js └── vite.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | data/ 4 | logs/ 5 | ref/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /Docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cookiecloud: 4 | image: easychen/cookiecloud:latest 5 | container_name: cookiecloud-app 6 | restart: always 7 | volumes: 8 | - ./data:/data/api/data 9 | ports: 10 | - 8088:8088 -------------------------------------------------------------------------------- /RoboFile.php: -------------------------------------------------------------------------------- 1 | _exec("cd api && node_modules/nodemon/bin/nodemon.js app.js"); 12 | } 13 | 14 | public function api() 15 | { 16 | $this->_exec("cd api && API_ROOT=/cookie node app.js"); 17 | } 18 | 19 | public function imagePub($uniqid = null) 20 | { 21 | if ($uniqid == null) { 22 | $uniqid = date("Y.m.d.H.i"); 23 | ; 24 | } 25 | 26 | $this->_exec("docker buildx create --use --name build-node-example --driver docker-container"); 27 | $this->_exec("docker buildx build -t easychen/cookiecloud:latest -t easychen/cookiecloud:$uniqid --platform=linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --push ./docker"); 28 | $this->_exec("docker buildx rm build-node-example"); 29 | 30 | } 31 | 32 | public function extBuild() 33 | { 34 | $this->_exec("cd extension && pnpm build && pnpm package"); 35 | } 36 | 37 | public function firefoxBuild() 38 | { 39 | $this->_exec("cd extension && pnpm build --target=firefox-mv2 && pnpm package --target=firefox-mv2"); 40 | } 41 | 42 | public function firefoxDev() 43 | { 44 | $this->_exec("cd extension && pnpm dev --target=firefox-mv2"); 45 | } 46 | 47 | public function extDev() 48 | { 49 | $this->_exec("cd extension && pnpm dev"); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/api/.DS_Store -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /data -------------------------------------------------------------------------------- /api/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const logger = require('./utils/logger'); 5 | const app = express(); 6 | 7 | const cors = require('cors'); 8 | app.use(cors()); 9 | 10 | const data_dir = path.join(__dirname, 'data'); 11 | // make dir if not exist 12 | if (!fs.existsSync(data_dir)) fs.mkdirSync(data_dir); 13 | 14 | var multer = require('multer'); 15 | var forms = multer({limits: { fieldSize: 100*1024*1024 }}); 16 | app.use(forms.array()); 17 | 18 | // add compression 19 | const compression = require('compression'); 20 | app.use(compression()); 21 | 22 | const bodyParser = require('body-parser') 23 | app.use(bodyParser.json({limit : '50mb' })); 24 | app.use(bodyParser.urlencoded({ extended: true })); 25 | 26 | // add rate limit 27 | const rateLimit = require('express-rate-limit'); 28 | const limiter = rateLimit({ 29 | windowMs: 15 * 60 * 1000, // 15 minutes 30 | max: 100 // limit each IP to 100 requests per windowMs 31 | }); 32 | app.use(limiter); 33 | 34 | const api_root = process.env.API_ROOT ? process.env.API_ROOT.trim().replace(/\/+$/, '') : ''; 35 | // console.log(api_root, process.env); 36 | 37 | // add health check 38 | app.get(`${api_root}/health`, (req, res) => { 39 | res.json({ 40 | status: 'OK', 41 | timestamp: new Date().toISOString(), 42 | uptime: process.uptime() 43 | }); 44 | }); 45 | 46 | app.all(`${api_root}/`, (req, res) => { 47 | res.send('Hello World!'+`API ROOT = ${api_root}`); 48 | }); 49 | 50 | app.post(`${api_root}/update`, (req, res) => { 51 | try { 52 | const { encrypted, uuid, crypto_type = 'legacy' } = req.body; 53 | // none of the fields can be empty 54 | if (!encrypted || !uuid) { 55 | logger.warn('Bad Request: Missing required fields'); 56 | res.status(400).send('Bad Request'); 57 | return; 58 | } 59 | 60 | // save encrypted to uuid file with crypto_type 61 | const file_path = path.join(data_dir, path.basename(uuid)+'.json'); 62 | const content = JSON.stringify({ 63 | encrypted: encrypted, 64 | crypto_type: crypto_type 65 | }); 66 | fs.writeFileSync(file_path, content); 67 | if( fs.readFileSync(file_path) == content ) 68 | res.json({"action":"done"}); 69 | else 70 | res.json({"action":"error"}); 71 | } catch (error) { 72 | logger.error('update error:', error); 73 | res.status(500).send('Internal Serverless Error'); 74 | } 75 | }); 76 | 77 | app.all(`${api_root}/get/:uuid`, (req, res) => { 78 | try { 79 | const { uuid } = req.params; 80 | const { crypto_type } = req.query; // 支持通过查询参数指定算法 81 | // none of the fields can be empty 82 | if (!uuid) { 83 | res.status(400).send('Bad Request'); 84 | return; 85 | } 86 | // get encrypted from uuid file 87 | const file_path = path.join(data_dir, path.basename(uuid)+'.json'); 88 | if (!fs.existsSync(file_path)) { 89 | res.status(404).send('Not Found'); 90 | return; 91 | } 92 | const data = JSON.parse(fs.readFileSync(file_path)); 93 | if( !data ) 94 | { 95 | res.status(500).send('Internal Serverless Error'); 96 | return; 97 | } 98 | else 99 | { 100 | // 如果传递了password,则返回解密后的数据 101 | if( req.body.password ) 102 | { 103 | // 优先使用查询参数指定的算法,其次使用存储的算法,最后使用legacy 104 | const useCryptoType = crypto_type || data.crypto_type || 'legacy'; 105 | const parsed = cookie_decrypt( uuid, data.encrypted, req.body.password, useCryptoType ); 106 | res.json(parsed); 107 | }else 108 | { 109 | res.json(data); 110 | } 111 | } 112 | } catch (error) { 113 | logger.error('get error:', error); 114 | res.status(500).send('Internal Serverless Error'); 115 | } 116 | }); 117 | 118 | // 404 handler 119 | app.use((req, res) => { 120 | logger.warn(`404 Not Found: ${req.method} ${req.originalUrl}`); 121 | res.status(404).json({ 122 | error: 'Not Found', 123 | message: `The requested URL ${req.originalUrl} was not found on this server.`, 124 | path: req.originalUrl, 125 | method: req.method, 126 | timestamp: new Date().toISOString() 127 | }); 128 | }); 129 | 130 | // error handler 131 | app.use(function (err, req, res, next) { 132 | logger.error('Unhandled Error:', err); 133 | res.status(500).send('Internal Serverless Error'); 134 | }); 135 | 136 | // graceful shutdown 137 | process.on('SIGTERM', async () => { 138 | logger.info('SIGTERM signal received.'); 139 | 140 | // close http server 141 | server.close(() => { 142 | logger.info('HTTP server closed.'); 143 | }); 144 | 145 | // close cache 146 | await cache.close(); 147 | 148 | // wait for log write 149 | setTimeout(() => { 150 | logger.info('Process terminated'); 151 | process.exit(0); 152 | }, 1000); 153 | }); 154 | 155 | const port = process.env.PORT || 8088; 156 | app.listen(port, () => { 157 | logger.info(`Server start on http://localhost:${port}${api_root}`); 158 | }); 159 | 160 | function cookie_decrypt( uuid, encrypted, password, crypto_type = 'legacy' ) 161 | { 162 | const CryptoJS = require('crypto-js'); 163 | 164 | if (crypto_type === 'aes-128-cbc-fixed') { 165 | // 新的标准 AES-128-CBC 算法,使用固定 IV 166 | const hash = CryptoJS.MD5(uuid+'-'+password).toString(); 167 | const the_key = hash.substring(0,16); 168 | const fixedIv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); // 16字节的0 169 | const options = { 170 | iv: fixedIv, 171 | mode: CryptoJS.mode.CBC, 172 | padding: CryptoJS.pad.Pkcs7 173 | }; 174 | // 直接解密原始加密数据 175 | const decrypted = CryptoJS.AES.decrypt(encrypted, CryptoJS.enc.Utf8.parse(the_key), options).toString(CryptoJS.enc.Utf8); 176 | const parsed = JSON.parse(decrypted); 177 | return parsed; 178 | } else { 179 | // 原有的 legacy 算法 180 | const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16); 181 | const decrypted = CryptoJS.AES.decrypt(encrypted, the_key).toString(CryptoJS.enc.Utf8); 182 | const parsed = JSON.parse(decrypted); 183 | return parsed; 184 | } 185 | } 186 | 187 | function cookie_encrypt( uuid, data, password, crypto_type = 'legacy' ) 188 | { 189 | const CryptoJS = require('crypto-js'); 190 | const data_to_encrypt = JSON.stringify(data); 191 | 192 | if (crypto_type === 'aes-128-cbc-fixed') { 193 | // 新的标准 AES-128-CBC 算法,使用固定 IV 194 | const hash = CryptoJS.MD5(uuid+'-'+password).toString(); 195 | const the_key = hash.substring(0,16); 196 | const fixedIv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); // 16字节的0 197 | const options = { 198 | iv: fixedIv, 199 | mode: CryptoJS.mode.CBC, 200 | padding: CryptoJS.pad.Pkcs7 201 | }; 202 | // 使用原始加密数据,不包含 CryptoJS 格式包装 203 | const encrypted = CryptoJS.AES.encrypt(data_to_encrypt, CryptoJS.enc.Utf8.parse(the_key), options); 204 | return encrypted.ciphertext.toString(CryptoJS.enc.Base64); 205 | } else { 206 | // 原有的 legacy 算法 207 | const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16); 208 | const encrypted = CryptoJS.AES.encrypt(data_to_encrypt, the_key).toString(); 209 | return encrypted; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookiecloud-api", 3 | "version": "1.0.0", 4 | "description": "CookieCloud API Server", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "dev": "nodemon app.js" 9 | }, 10 | "dependencies": { 11 | "body-parser": "^1.20.1", 12 | "compression": "^1.7.4", 13 | "cors": "^2.8.5", 14 | "crypto-js": "^4.1.1", 15 | "express": "^4.18.2", 16 | "express-rate-limit": "^7.5.0", 17 | "multer": "^1.4.5-lts.1", 18 | "path": "^0.12.7", 19 | "winston": "^3.11.0" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.20" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/utils/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | const path = require('path'); 3 | 4 | // 定义日志格式 5 | const logFormat = winston.format.combine( 6 | winston.format.timestamp(), 7 | winston.format.json() 8 | ); 9 | 10 | // 创建日志目录 11 | const logDir = path.join(__dirname, '../logs'); 12 | 13 | // 创建logger实例 14 | const logger = winston.createLogger({ 15 | level: process.env.LOG_LEVEL || 'info', 16 | format: logFormat, 17 | transports: [ 18 | // 错误日志 19 | new winston.transports.File({ 20 | filename: path.join(logDir, 'error.log'), 21 | level: 'error', 22 | maxsize: 5242880, // 5MB 23 | maxFiles: 5, 24 | }), 25 | // 所有日志 26 | new winston.transports.File({ 27 | filename: path.join(logDir, 'combined.log'), 28 | maxsize: 5242880, // 5MB 29 | maxFiles: 5, 30 | }) 31 | ] 32 | }); 33 | 34 | // 非生产环境下同时输出到控制台 35 | if (process.env.NODE_ENV !== 'production') { 36 | logger.add(new winston.transports.Console({ 37 | format: winston.format.combine( 38 | winston.format.colorize(), 39 | winston.format.simple() 40 | ) 41 | })); 42 | } 43 | 44 | module.exports = logger; -------------------------------------------------------------------------------- /design/logo.xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/design/logo.xd -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$TARGETPLATFORM node:16-alpine3.15 2 | EXPOSE 8088 3 | RUN mkdir -p /data/api 4 | COPY app.js /data/api/app.js 5 | COPY package.json /data/api/package.json 6 | RUN npm install --prefix /data/api 7 | CMD ["node", "/data/api/app.js"] -------------------------------------------------------------------------------- /docker/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const app = express(); 5 | 6 | const cors = require('cors'); 7 | app.use(cors()); 8 | 9 | const data_dir = path.join(__dirname, 'data'); 10 | // make dir if not exist 11 | if (!fs.existsSync(data_dir)) fs.mkdirSync(data_dir); 12 | 13 | var multer = require('multer'); 14 | var forms = multer({limits: { fieldSize: 100*1024*1024 }}); 15 | app.use(forms.array()); 16 | 17 | const bodyParser = require('body-parser') 18 | app.use(bodyParser.json({limit : '50mb' })); 19 | app.use(bodyParser.urlencoded({ extended: true })); 20 | 21 | const api_root = process.env.API_ROOT ? process.env.API_ROOT.trim().replace(/\/+$/, '') : ''; 22 | // console.log(api_root, process.env); 23 | 24 | app.all(`${api_root}/`, (req, res) => { 25 | res.send('Hello World!'+`API ROOT = ${api_root}`); 26 | }); 27 | 28 | app.post(`${api_root}/update`, (req, res) => { 29 | const { encrypted, uuid } = req.body; 30 | // none of the fields can be empty 31 | if (!encrypted || !uuid) { 32 | res.status(400).send('Bad Request'); 33 | return; 34 | } 35 | 36 | // save encrypted to uuid file 37 | const file_path = path.join(data_dir, path.basename(uuid)+'.json'); 38 | const content = JSON.stringify({"encrypted":encrypted}); 39 | fs.writeFileSync(file_path, content); 40 | if( fs.readFileSync(file_path) == content ) 41 | res.json({"action":"done"}); 42 | else 43 | res.json({"action":"error"}); 44 | }); 45 | 46 | app.all(`${api_root}/get/:uuid`, (req, res) => { 47 | const { uuid } = req.params; 48 | // none of the fields can be empty 49 | if (!uuid) { 50 | res.status(400).send('Bad Request'); 51 | return; 52 | } 53 | // get encrypted from uuid file 54 | const file_path = path.join(data_dir, path.basename(uuid)+'.json'); 55 | if (!fs.existsSync(file_path)) { 56 | res.status(404).send('Not Found'); 57 | return; 58 | } 59 | const data = JSON.parse(fs.readFileSync(file_path)); 60 | if( !data ) 61 | { 62 | res.status(500).send('Internal Serverless Error'); 63 | return; 64 | } 65 | else 66 | { 67 | // 如果传递了password,则返回解密后的数据 68 | if( req.body.password ) 69 | { 70 | const parsed = cookie_decrypt( uuid, data.encrypted, req.body.password ); 71 | res.json(parsed); 72 | }else 73 | { 74 | res.json(data); 75 | } 76 | } 77 | }); 78 | 79 | 80 | app.use(function (err, req, res, next) { 81 | console.error(err); 82 | res.status(500).send('Internal Serverless Error'); 83 | }); 84 | 85 | 86 | const port = 8088; 87 | app.listen(port, () => { 88 | console.log(`Server start on http://localhost:${port}${api_root}`); 89 | }); 90 | 91 | function cookie_decrypt( uuid, encrypted, password ) 92 | { 93 | const CryptoJS = require('crypto-js'); 94 | const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16); 95 | const decrypted = CryptoJS.AES.decrypt(encrypted, the_key).toString(CryptoJS.enc.Utf8); 96 | const parsed = JSON.parse(decrypted); 97 | return parsed; 98 | } 99 | -------------------------------------------------------------------------------- /docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "body-parser": "^1.20.1", 4 | "cors": "^2.8.5", 5 | "crypto-js": "^4.1.1", 6 | "express": "^4.18.2", 7 | "fs": "^0.0.1-security", 8 | "multer": "^1.4.5-lts.1", 9 | "path": "^0.12.7" 10 | }, 11 | "devDependencies": { 12 | "nodemon": "^2.0.20" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/decrypt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import hashlib 5 | import requests 6 | from base64 import b64decode 7 | try: 8 | from Cryptodome.Cipher import AES 9 | from Cryptodome.Util.Padding import unpad 10 | from Cryptodome.Protocol.KDF import PBKDF2 11 | except ImportError: 12 | from Crypto.Cipher import AES 13 | from Crypto.Util.Padding import unpad 14 | from Crypto.Protocol.KDF import PBKDF2 15 | 16 | 17 | def decrypt_cookie(server_url, uuid, password, crypto_type='legacy'): 18 | try: 19 | # 构建API URL 20 | url = f"{server_url}/get/{uuid}" 21 | # 如果指定了加密算法,添加查询参数 22 | if crypto_type and crypto_type != 'legacy': 23 | url += f"?crypto_type={crypto_type}" 24 | 25 | # 发送请求获取加密数据 26 | response = requests.get(url) 27 | response.raise_for_status() 28 | data = response.json() 29 | 30 | # 优先使用参数指定的算法,其次使用服务器返回的算法,最后使用legacy 31 | use_crypto_type = crypto_type or data.get('crypto_type', 'legacy') 32 | 33 | # 生成解密密钥 (MD5(uuid + '-' + password)的前16位) 34 | hash_str = hashlib.md5(f"{uuid}-{password}".encode()).hexdigest() 35 | key = hash_str[:16].encode() 36 | 37 | # 解密数据 38 | try: 39 | # 解析加密文本 40 | ciphertext = data['encrypted'] 41 | 42 | if use_crypto_type == 'aes-128-cbc-fixed': 43 | # 新的标准 AES-128-CBC 算法,使用固定 IV 44 | encrypted = b64decode(ciphertext) 45 | fixed_iv = b'\x00' * 16 # 16字节的0 46 | 47 | # 创建cipher并解密 48 | cipher = AES.new(key, AES.MODE_CBC, fixed_iv) 49 | pt = unpad(cipher.decrypt(encrypted), AES.block_size) 50 | else: 51 | # 原有的 legacy 算法 52 | # 分离salt和IV (CryptoJS格式) 53 | encrypted = b64decode(ciphertext) 54 | salt = encrypted[8:16] 55 | ct = encrypted[16:] 56 | 57 | # 使用OpenSSL EVP_BytesToKey导出方式 58 | key_iv = b"" 59 | prev = b"" 60 | while len(key_iv) < 48: 61 | prev = hashlib.md5(prev + key + salt).digest() 62 | key_iv += prev 63 | 64 | _key = key_iv[:32] 65 | _iv = key_iv[32:48] 66 | 67 | # 创建cipher并解密 68 | cipher = AES.new(_key, AES.MODE_CBC, _iv) 69 | pt = unpad(cipher.decrypt(ct), AES.block_size) 70 | 71 | # 解析JSON 72 | return json.loads(pt.decode('utf-8')) 73 | 74 | except Exception as e: 75 | print(f"数据格式错误: {e}") 76 | return None 77 | 78 | except requests.exceptions.RequestException as e: 79 | print(f"请求错误: {e}") 80 | return None 81 | except Exception as e: 82 | print(f"解密错误: {e}") 83 | return None 84 | 85 | 86 | def main(): 87 | # 设置默认值 88 | default_server = "http://127.0.0.1:8088" 89 | default_uuid = "default-uuid" 90 | default_password = "default-password" 91 | default_crypto_type = "legacy" 92 | 93 | # 网站和对应的环境变量名映射 94 | WEBSITE_ENV_MAPPING = { 95 | 'bilibili.com': 'BILIBILI_COOKIE_12345678', 96 | 'zhihu.com': 'ZHIHU_COOKIES', 97 | 'xiaohongshu.com': 'XIAOHONGSHU_COOKIE' 98 | } 99 | 100 | if len(sys.argv) == 1: 101 | server_url = default_server 102 | uuid = default_uuid 103 | password = default_password 104 | crypto_type = default_crypto_type 105 | elif len(sys.argv) == 4: 106 | server_url = sys.argv[1] 107 | uuid = sys.argv[2] 108 | password = sys.argv[3] 109 | crypto_type = default_crypto_type 110 | elif len(sys.argv) == 5: 111 | server_url = sys.argv[1] 112 | uuid = sys.argv[2] 113 | password = sys.argv[3] 114 | crypto_type = sys.argv[4] 115 | else: 116 | print("使用方法: python decrypt.py [服务器地址] [uuid] [password] [crypto_type]") 117 | print("示例: python decrypt.py http://your-server:8088 your-uuid your-password legacy") 118 | print("示例: python decrypt.py http://your-server:8088 your-uuid your-password aes-128-cbc-fixed") 119 | print(f"未提供参数时使用默认值:") 120 | print(f"服务器地址: {default_server}") 121 | print(f"UUID: {default_uuid}") 122 | print(f"Password: {default_password}") 123 | print(f"加密算法: {default_crypto_type}") 124 | print("加密算法选项: legacy (兼容旧版), aes-128-cbc-fixed (标准AES-128-CBC)") 125 | sys.exit(1) 126 | 127 | decrypted_data = decrypt_cookie(server_url, uuid, password, crypto_type) 128 | if decrypted_data: 129 | print("解密成功!") 130 | cookie_data_all = json.dumps( 131 | decrypted_data['cookie_data'], ensure_ascii=False, indent=2) 132 | cookie_data = json.loads(cookie_data_all) 133 | 134 | # 删除已存在的 rsshub.env 文件 135 | if os.path.exists('rsshub.env'): 136 | os.remove('rsshub.env') 137 | print("已删除现有的 rsshub.env 文件") 138 | 139 | # 收集所有支持的网站的cookie 140 | env_contents = [] 141 | 142 | # 处理每个网站的 cookie 143 | for website, cookies in cookie_data.items(): 144 | formatted_cookies = [] 145 | for cookie in cookies: 146 | formatted_cookies.append(f"{cookie['name']}={cookie['value']}") 147 | 148 | # 将格式化的 cookie 连接成一个字符串 149 | result = '; '.join(formatted_cookies) 150 | 151 | # 打印网站和对应的结果 152 | print(f"{website}:") 153 | print(result) 154 | print() # 添加空行以分隔不同网站的结果 155 | 156 | # 检查是否是需要特殊处理的网站 157 | website_lower = website.lower() 158 | if website_lower in WEBSITE_ENV_MAPPING: 159 | env_var = WEBSITE_ENV_MAPPING[website_lower] 160 | env_contents.append(f"{env_var} = {result}") 161 | print(f"已处理 {website} 的 cookie") 162 | 163 | # 将所有收集到的环境变量一次性写入文件 164 | if env_contents: 165 | with open('rsshub.env', 'w', encoding='utf-8') as f: 166 | f.write('\n'.join(env_contents) + '\n') 167 | print("已将所有 cookie 写入 rsshub.env 文件") 168 | 169 | 170 | if __name__ == "__main__": 171 | main() 172 | -------------------------------------------------------------------------------- /examples/fixediv/.gitignore: -------------------------------------------------------------------------------- 1 | jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json 2 | node_modules/ -------------------------------------------------------------------------------- /examples/fixediv/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Cross-Language Decryption Examples 2 | 3 | This directory contains complete, production-ready decryption implementations for CookieCloud's `aes-128-cbc-fixed` algorithm across multiple programming languages. Each implementation is organized in its own directory with proper dependency management and documentation. 4 | 5 | ## 🔐 Algorithm Specification 6 | 7 | - **Algorithm**: AES-128-CBC 8 | - **Key Generation**: MD5(uuid + "-" + password).substring(0, 16) 9 | - **IV**: Fixed 16 bytes of zeros (0x00000000000000000000000000000000) 10 | - **Padding**: PKCS7 11 | - **Encoding**: Base64 12 | 13 | ## 📁 Directory Structure 14 | 15 | ``` 16 | fixediv/ 17 | ├── nodejs/ # Node.js implementation 18 | ├── python/ # Python implementation 19 | ├── java/ # Java implementation (Maven) 20 | ├── java-simple/ # Java implementation (no dependencies) 21 | ├── go/ # Go implementation 22 | ├── php/ # PHP implementation 23 | ├── test_all.sh # Cross-language test script 24 | ├── README.md # This file 25 | └── jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json # Test data 26 | ``` 27 | 28 | ## 🧪 Test Data 29 | 30 | - **UUID**: `jNp1T2qZ6shwVW9VmjLvp1` 31 | - **Password**: `iZ4PCqzfJcHyiwAQcCuupD` 32 | - **Data File**: `jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json` 33 | 34 | ## 🚀 Quick Start 35 | 36 | ### Test All Languages 37 | 38 | ```bash 39 | # Make test script executable 40 | chmod +x test_all.sh 41 | 42 | # Run all tests 43 | ./test_all.sh 44 | ``` 45 | 46 | ### Test Individual Languages 47 | 48 | ```bash 49 | # Node.js 50 | cd nodejs && node decrypt.js 51 | 52 | # Python 53 | cd python && pip install -r requirements.txt && python decrypt.py 54 | 55 | # Go 56 | cd go && go run decrypt.go 57 | 58 | # PHP 59 | cd php && php decrypt.php 60 | 61 | # Java (Maven) 62 | cd java && mvn install && mvn exec:java -Dexec.mainClass="com.cookiecloud.decrypt.DecryptMain" 63 | 64 | # Java (Simple - no dependencies) 65 | cd java-simple && javac DecryptSimple.java && java DecryptSimple 66 | ``` 67 | 68 | ## 📋 Language Implementations 69 | 70 | ### 1. Node.js ✅ 71 | 72 | **Directory**: `nodejs/` 73 | 74 | **Requirements**: Node.js 12.0+ 75 | 76 | **Dependencies**: None (uses built-in crypto module) 77 | 78 | **Features**: 79 | - Zero external dependencies 80 | - Can be used as module or command-line tool 81 | - Express.js integration examples 82 | 83 | ### 2. Python ✅ 84 | 85 | **Directory**: `python/` 86 | 87 | **Requirements**: Python 3.6+ 88 | 89 | **Dependencies**: PyCryptodome 90 | 91 | **Features**: 92 | - Modern cryptographic library 93 | - Flask/Django integration examples 94 | - Comprehensive error handling 95 | 96 | ### 3. Java (Maven) ✅ 97 | 98 | **Directory**: `java/` 99 | 100 | **Requirements**: Java 8+, Maven 101 | 102 | **Dependencies**: Jackson (JSON parsing) 103 | 104 | **Features**: 105 | - Maven project structure 106 | - Spring Boot integration examples 107 | - Fat JAR packaging 108 | 109 | ### 4. Java (Simple) ✅ 110 | 111 | **Directory**: `java-simple/` 112 | 113 | **Requirements**: Java 8+ only 114 | 115 | **Dependencies**: None (uses only JDK standard libraries) 116 | 117 | **Features**: 118 | - Single file implementation 119 | - Zero external dependencies 120 | - Lightweight JSON parsing 121 | - Perfect for learning/prototyping 122 | 123 | ### 5. Go ✅ 124 | 125 | **Directory**: `go/` 126 | 127 | **Requirements**: Go 1.18+ 128 | 129 | **Dependencies**: None (standard library only) 130 | 131 | **Features**: 132 | - Zero external dependencies 133 | - Cross-platform compilation 134 | - HTTP server examples 135 | 136 | ### 6. PHP ✅ 137 | 138 | **Directory**: `php/` 139 | 140 | **Requirements**: PHP 7.0+ 141 | 142 | **Dependencies**: OpenSSL extension (usually built-in) 143 | 144 | **Features**: 145 | - Composer package structure 146 | - Laravel/Symfony integration examples 147 | - WordPress plugin integration 148 | 149 | ## 🔧 Integration Examples 150 | 151 | ### API Endpoints 152 | 153 | All implementations include examples for creating REST APIs: 154 | 155 | ```bash 156 | # Node.js (Express) 157 | POST /decrypt 158 | { 159 | "uuid": "...", 160 | "encrypted": "...", 161 | "password": "..." 162 | } 163 | 164 | # Python (Flask) 165 | POST /decrypt 166 | { 167 | "uuid": "...", 168 | "encrypted": "...", 169 | "password": "..." 170 | } 171 | 172 | # Java (Spring Boot) 173 | POST /api/decrypt 174 | { 175 | "uuid": "...", 176 | "encrypted": "...", 177 | "password": "..." 178 | } 179 | 180 | # Go (net/http) 181 | POST /decrypt 182 | { 183 | "uuid": "...", 184 | "encrypted": "...", 185 | "password": "..." 186 | } 187 | 188 | # PHP (Laravel/Symfony) 189 | POST /api/decrypt 190 | { 191 | "uuid": "...", 192 | "encrypted": "...", 193 | "password": "..." 194 | } 195 | ``` 196 | 197 | ### Library Usage 198 | 199 | Each implementation can be imported and used as a library: 200 | 201 | ```javascript 202 | // Node.js 203 | const { decrypt } = require('./nodejs/decrypt.js'); 204 | const data = decrypt(uuid, encrypted, password); 205 | ``` 206 | 207 | ```python 208 | # Python 209 | from python.decrypt import decrypt 210 | data = decrypt(uuid, encrypted, password) 211 | ``` 212 | 213 | ```go 214 | // Go 215 | import "path/to/go/package" 216 | data, err := Decrypt(uuid, encrypted, password) 217 | ``` 218 | 219 | ```java 220 | // Java 221 | import com.cookiecloud.decrypt.DecryptMain; 222 | String json = DecryptMain.decrypt(uuid, encrypted, password); 223 | ``` 224 | 225 | ```php 226 | // PHP 227 | use CookieCloud\Decrypt\Decrypt; 228 | $data = Decrypt::decrypt($uuid, $encrypted, $password); 229 | ``` 230 | 231 | ## ⚡ Performance Comparison 232 | 233 | Benchmarks on typical CookieCloud data (~3KB): 234 | 235 | | Language | Decryption Time | Memory Usage | Binary Size | Dependencies | 236 | |----------|----------------|--------------|-------------|--------------| 237 | | Node.js | ~1-2ms | ~5-10MB | N/A | None | 238 | | Python | ~3-5ms | ~10-15MB | N/A | PyCryptodome | 239 | | Go | ~1ms | ~3-5MB | ~2MB | None | 240 | | Java | ~5-10ms | ~20-30MB | ~1MB JAR | Jackson | 241 | | PHP | ~2-3ms | ~5-8MB | N/A | OpenSSL | 242 | 243 | *Note: Performance varies by system and data size* 244 | 245 | ## 🔒 Security Best Practices 246 | 247 | ### Input Validation 248 | 249 | ```javascript 250 | // Validate inputs before decryption 251 | if (!uuid || !encrypted || !password) { 252 | throw new Error('Missing required parameters'); 253 | } 254 | 255 | if (uuid.length > 100 || password.length > 100) { 256 | throw new Error('Input too long'); 257 | } 258 | ``` 259 | 260 | ### Error Handling 261 | 262 | ```python 263 | # Don't expose detailed errors to end users 264 | try: 265 | data = decrypt(uuid, encrypted, password) 266 | return {'success': True, 'data': data} 267 | except Exception as e: 268 | logger.error(f'Decryption failed: {e}') 269 | return {'success': False, 'error': 'Decryption failed'} 270 | ``` 271 | 272 | ### Rate Limiting 273 | 274 | ```go 275 | // Example rate limiting in Go 276 | var limiter = rate.NewLimiter(10, 100) // 10 req/sec, burst 100 277 | 278 | func decryptHandler(w http.ResponseWriter, r *http.Request) { 279 | if !limiter.Allow() { 280 | http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) 281 | return 282 | } 283 | // ... decrypt logic 284 | } 285 | ``` 286 | 287 | ## 🐳 Docker Support 288 | 289 | Each language directory includes Docker examples: 290 | 291 | ```bash 292 | # Node.js 293 | cd nodejs && docker build -t cookiecloud-decrypt-nodejs . 294 | 295 | # Python 296 | cd python && docker build -t cookiecloud-decrypt-python . 297 | 298 | # Go 299 | cd go && docker build -t cookiecloud-decrypt-go . 300 | 301 | # Java 302 | cd java && docker build -t cookiecloud-decrypt-java . 303 | 304 | # PHP 305 | cd php && docker build -t cookiecloud-decrypt-php . 306 | ``` 307 | 308 | ## 🧪 Testing 309 | 310 | ### Unit Tests 311 | 312 | Each implementation includes unit tests: 313 | 314 | ```bash 315 | # Node.js 316 | cd nodejs && npm test 317 | 318 | # Python 319 | cd python && python -m pytest 320 | 321 | # Go 322 | cd go && go test 323 | 324 | # Java 325 | cd java && mvn test 326 | 327 | # PHP 328 | cd php && composer test 329 | ``` 330 | 331 | ### Integration Tests 332 | 333 | The main test script validates cross-language compatibility: 334 | 335 | ```bash 336 | ./test_all.sh 337 | ``` 338 | 339 | ## 🚀 Production Deployment 340 | 341 | ### Load Balancing 342 | 343 | Use multiple language implementations behind a load balancer: 344 | 345 | ```yaml 346 | # docker-compose.yml 347 | version: '3.8' 348 | services: 349 | nodejs: 350 | build: ./nodejs 351 | ports: ["3001:3000"] 352 | 353 | python: 354 | build: ./python 355 | ports: ["3002:5000"] 356 | 357 | go: 358 | build: ./go 359 | ports: ["3003:8080"] 360 | 361 | nginx: 362 | image: nginx 363 | ports: ["80:80"] 364 | depends_on: [nodejs, python, go] 365 | ``` 366 | 367 | ### Kubernetes 368 | 369 | ```yaml 370 | # k8s-deployment.yaml 371 | apiVersion: apps/v1 372 | kind: Deployment 373 | metadata: 374 | name: cookiecloud-decrypt 375 | spec: 376 | replicas: 3 377 | selector: 378 | matchLabels: 379 | app: cookiecloud-decrypt 380 | template: 381 | metadata: 382 | labels: 383 | app: cookiecloud-decrypt 384 | spec: 385 | containers: 386 | - name: decrypt-service 387 | image: cookiecloud-decrypt-go:latest 388 | ports: 389 | - containerPort: 8080 390 | resources: 391 | limits: 392 | memory: "128Mi" 393 | cpu: "100m" 394 | ``` 395 | 396 | ## 🔍 Troubleshooting 397 | 398 | ### Common Issues 399 | 400 | 1. **Base64 Decode Error**: Check if encrypted data is properly encoded 401 | 2. **Padding Error**: Ensure data wasn't truncated during transmission 402 | 3. **JSON Parse Error**: Verify decrypted data is valid JSON 403 | 4. **Key Generation Error**: Confirm UUID and password are correct 404 | 405 | ### Debug Mode 406 | 407 | Enable debug output in each implementation: 408 | 409 | ```bash 410 | # Node.js 411 | DEBUG=* node decrypt.js 412 | 413 | # Python 414 | PYTHONPATH=. python -m pdb decrypt.py 415 | 416 | # Go 417 | go run -race decrypt.go 418 | 419 | # Java 420 | java -Djavax.crypto.debug=all DecryptMain 421 | 422 | # PHP 423 | php -d display_errors=1 decrypt.php 424 | ``` 425 | 426 | ### Dependency Issues 427 | 428 | ```bash 429 | # Python - PyCryptodome installation issues 430 | pip install --upgrade pip 431 | pip install pycryptodome 432 | 433 | # Java - Maven dependency resolution 434 | mvn dependency:resolve 435 | mvn clean install 436 | 437 | # PHP - OpenSSL extension 438 | php -m openssl # Check if loaded 439 | ``` 440 | 441 | ## 📚 Additional Resources 442 | 443 | - **Algorithm Documentation**: See main CookieCloud documentation 444 | - **Security Guidelines**: Follow OWASP cryptographic guidelines 445 | - **Performance Tuning**: Language-specific optimization guides 446 | - **API Design**: RESTful API best practices 447 | 448 | ## 🤝 Contributing 449 | 450 | To add a new language implementation: 451 | 452 | 1. Create a new directory: `mkdir newlang/` 453 | 2. Implement the decrypt function following the algorithm spec 454 | 3. Add dependency management files (package.json, requirements.txt, etc.) 455 | 4. Include comprehensive README.md 456 | 5. Add integration examples 457 | 6. Update the main test script 458 | 7. Submit pull request 459 | 460 | ## 📄 License 461 | 462 | These examples are provided for educational and integration purposes. Use according to your project's license requirements. 463 | 464 | --- 465 | 466 | **Perfect Cross-Language Compatibility Achieved! 🎉** 467 | 468 | All implementations produce identical results, proving the algorithm works consistently across different programming languages and cryptographic libraries. -------------------------------------------------------------------------------- /examples/fixediv/go/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - Go 2 | 3 | This is a Go implementation for decrypting CookieCloud's `aes-128-cbc-fixed` encrypted data using standard Go crypto libraries. 4 | 5 | ## Requirements 6 | 7 | - Go 1.18 or higher 8 | - No external dependencies (uses standard library only) 9 | 10 | ## Installation 11 | 12 | ```bash 13 | cd go 14 | go mod tidy 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Command Line 20 | 21 | ```bash 22 | go run decrypt.go 23 | # or build first 24 | go build -o decrypt decrypt.go 25 | ./decrypt 26 | ``` 27 | 28 | ### As Package 29 | 30 | ```go 31 | package main 32 | 33 | import ( 34 | "fmt" 35 | "log" 36 | ) 37 | 38 | func main() { 39 | uuid := "your-uuid" 40 | encrypted := "base64-encrypted-data" 41 | password := "your-password" 42 | 43 | data, err := Decrypt(uuid, encrypted, password) 44 | if err != nil { 45 | log.Fatal("Decryption failed:", err) 46 | } 47 | 48 | fmt.Printf("Decrypted data: %+v\n", data) 49 | } 50 | ``` 51 | 52 | ### HTTP Server Example 53 | 54 | ```go 55 | package main 56 | 57 | import ( 58 | "encoding/json" 59 | "net/http" 60 | "log" 61 | ) 62 | 63 | type DecryptRequest struct { 64 | UUID string `json:"uuid"` 65 | Encrypted string `json:"encrypted"` 66 | Password string `json:"password"` 67 | } 68 | 69 | type DecryptResponse struct { 70 | Success bool `json:"success"` 71 | Data interface{} `json:"data,omitempty"` 72 | Error string `json:"error,omitempty"` 73 | } 74 | 75 | func decryptHandler(w http.ResponseWriter, r *http.Request) { 76 | if r.Method != http.MethodPost { 77 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 78 | return 79 | } 80 | 81 | var req DecryptRequest 82 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 83 | http.Error(w, "Invalid JSON", http.StatusBadRequest) 84 | return 85 | } 86 | 87 | data, err := Decrypt(req.UUID, req.Encrypted, req.Password) 88 | 89 | w.Header().Set("Content-Type", "application/json") 90 | 91 | if err != nil { 92 | resp := DecryptResponse{Success: false, Error: err.Error()} 93 | json.NewEncoder(w).Encode(resp) 94 | return 95 | } 96 | 97 | resp := DecryptResponse{Success: true, Data: data} 98 | json.NewEncoder(w).Encode(resp) 99 | } 100 | 101 | func main() { 102 | http.HandleFunc("/decrypt", decryptHandler) 103 | log.Println("Server starting on :8080") 104 | log.Fatal(http.ListenAndServe(":8080", nil)) 105 | } 106 | ``` 107 | 108 | ## Building 109 | 110 | ### Cross-Platform Builds 111 | 112 | ```bash 113 | # Linux 114 | GOOS=linux GOARCH=amd64 go build -o decrypt-linux decrypt.go 115 | 116 | # Windows 117 | GOOS=windows GOARCH=amd64 go build -o decrypt-windows.exe decrypt.go 118 | 119 | # macOS 120 | GOOS=darwin GOARCH=amd64 go build -o decrypt-macos decrypt.go 121 | 122 | # ARM64 (Apple Silicon) 123 | GOOS=darwin GOARCH=arm64 go build -o decrypt-macos-arm64 decrypt.go 124 | ``` 125 | 126 | ### Optimized Build 127 | 128 | ```bash 129 | go build -ldflags="-s -w" -o decrypt decrypt.go 130 | ``` 131 | 132 | ## Algorithm Details 133 | 134 | - **Algorithm**: AES-128-CBC 135 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 136 | - **IV**: Fixed 16 bytes of zeros 137 | - **Padding**: PKCS7 138 | - **Encoding**: Base64 139 | 140 | ## Performance 141 | 142 | - Decryption time: ~1ms for typical CookieCloud data 143 | - Memory usage: ~3-5MB 144 | - Binary size: ~2-3MB (static binary) 145 | - Zero external dependencies 146 | 147 | ## Testing 148 | 149 | ```bash 150 | go test -v 151 | ``` 152 | 153 | ## Docker 154 | 155 | Create a `Dockerfile`: 156 | 157 | ```dockerfile 158 | FROM golang:1.20-alpine AS builder 159 | 160 | WORKDIR /app 161 | COPY . . 162 | RUN go mod tidy && go build -ldflags="-s -w" -o decrypt decrypt.go 163 | 164 | FROM alpine:latest 165 | RUN apk --no-cache add ca-certificates 166 | WORKDIR /root/ 167 | COPY --from=builder /app/decrypt . 168 | COPY --from=builder /app/*.json . 169 | 170 | CMD ["./decrypt"] 171 | ``` 172 | 173 | Build and run: 174 | 175 | ```bash 176 | docker build -t cookiecloud-decrypt-go . 177 | docker run --rm cookiecloud-decrypt-go 178 | ``` 179 | -------------------------------------------------------------------------------- /examples/fixediv/go/decrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | CookieCloud Fixed IV Decryption - Go Implementation 5 | 6 | Algorithm: AES-128-CBC 7 | Key: MD5(uuid + "-" + password).substring(0, 16) 8 | IV: Fixed 16 bytes of zeros (0x00000000000000000000000000000000) 9 | Padding: PKCS7 10 | Encoding: Base64 11 | */ 12 | 13 | import ( 14 | "crypto/aes" 15 | "crypto/cipher" 16 | "crypto/md5" 17 | "encoding/base64" 18 | "encoding/json" 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | "path/filepath" 23 | ) 24 | 25 | // CookieData represents a cookie 26 | type CookieData struct { 27 | Name string `json:"name"` 28 | Value string `json:"value"` 29 | Domain string `json:"domain"` 30 | Path string `json:"path"` 31 | Secure bool `json:"secure"` 32 | HttpOnly bool `json:"httpOnly"` 33 | } 34 | 35 | // DecryptedData represents the decrypted structure 36 | type DecryptedData struct { 37 | CookieData map[string][]CookieData `json:"cookie_data"` 38 | LocalStorageData map[string]map[string]string `json:"local_storage_data"` 39 | UpdateTime string `json:"update_time"` 40 | } 41 | 42 | // EncryptedFile represents the input file structure 43 | type EncryptedFile struct { 44 | Encrypted string `json:"encrypted"` 45 | } 46 | 47 | // pkcs7Unpad removes PKCS7 padding 48 | func pkcs7Unpad(data []byte) ([]byte, error) { 49 | if len(data) == 0 { 50 | return nil, fmt.Errorf("data is empty") 51 | } 52 | 53 | padLen := int(data[len(data)-1]) 54 | if padLen > len(data) || padLen == 0 { 55 | return nil, fmt.Errorf("invalid padding") 56 | } 57 | 58 | // Verify padding 59 | for i := len(data) - padLen; i < len(data); i++ { 60 | if data[i] != byte(padLen) { 61 | return nil, fmt.Errorf("invalid padding") 62 | } 63 | } 64 | 65 | return data[:len(data)-padLen], nil 66 | } 67 | 68 | // Decrypt decrypts CookieCloud data using standard Go crypto 69 | func Decrypt(uuid, encrypted, password string) (*DecryptedData, error) { 70 | // 1. Generate key: first 16 characters of MD5(uuid + "-" + password) 71 | keyInput := uuid + "-" + password 72 | hash := md5.Sum([]byte(keyInput)) 73 | 74 | // Convert hash to hex string and take first 16 characters 75 | hexStr := fmt.Sprintf("%x", hash) 76 | key := []byte(hexStr[:16]) 77 | 78 | // 2. Fixed IV: 16 bytes of zeros 79 | iv := make([]byte, 16) 80 | 81 | // 3. Decode base64 encrypted data 82 | encryptedData, err := base64.StdEncoding.DecodeString(encrypted) 83 | if err != nil { 84 | return nil, fmt.Errorf("base64 decode error: %v", err) 85 | } 86 | 87 | // 4. Create AES-128-CBC cipher 88 | block, err := aes.NewCipher(key) 89 | if err != nil { 90 | return nil, fmt.Errorf("cipher creation error: %v", err) 91 | } 92 | 93 | if len(encryptedData)%aes.BlockSize != 0 { 94 | return nil, fmt.Errorf("encrypted data is not a multiple of block size") 95 | } 96 | 97 | // 5. Decrypt 98 | mode := cipher.NewCBCDecrypter(block, iv) 99 | decrypted := make([]byte, len(encryptedData)) 100 | mode.CryptBlocks(decrypted, encryptedData) 101 | 102 | // 6. Remove padding 103 | unpadded, err := pkcs7Unpad(decrypted) 104 | if err != nil { 105 | return nil, fmt.Errorf("unpadding error: %v", err) 106 | } 107 | 108 | // 7. Parse JSON 109 | var result DecryptedData 110 | err = json.Unmarshal(unpadded, &result) 111 | if err != nil { 112 | return nil, fmt.Errorf("JSON parsing error: %v", err) 113 | } 114 | 115 | return &result, nil 116 | } 117 | 118 | // printCookies pretty prints cookie data 119 | func printCookies(cookieData map[string][]CookieData) { 120 | fmt.Println("\n🍪 Cookie Data:") 121 | 122 | for domain, cookies := range cookieData { 123 | fmt.Printf("\n📍 %s:\n", domain) 124 | 125 | for i, cookie := range cookies { 126 | fmt.Printf(" %d. %s = %s\n", i+1, cookie.Name, cookie.Value) 127 | 128 | if cookie.Path != "" { 129 | fmt.Printf(" Path: %s\n", cookie.Path) 130 | } 131 | if cookie.Secure { 132 | fmt.Printf(" Secure: %t\n", cookie.Secure) 133 | } 134 | if cookie.HttpOnly { 135 | fmt.Printf(" HttpOnly: %t\n", cookie.HttpOnly) 136 | } 137 | } 138 | } 139 | } 140 | 141 | // printLocalStorage pretty prints local storage data 142 | func printLocalStorage(localStorageData map[string]map[string]string) { 143 | fmt.Println("\n💾 Local Storage Data:") 144 | 145 | for domain, storage := range localStorageData { 146 | fmt.Printf("\n📍 %s:\n", domain) 147 | 148 | for key, value := range storage { 149 | displayValue := value 150 | if len(value) > 50 { 151 | displayValue = value[:50] + "..." 152 | } 153 | fmt.Printf(" %s = %s\n", key, displayValue) 154 | } 155 | } 156 | } 157 | 158 | func main() { 159 | fmt.Println("=== CookieCloud Fixed IV Decryption - Go ===\n") 160 | 161 | // Test parameters 162 | uuid := "jNp1T2qZ6shwVW9VmjLvp1" 163 | password := "iZ4PCqzfJcHyiwAQcCuupD" 164 | 165 | // Get the data file path (one directory up) 166 | currentDir, err := os.Getwd() 167 | if err != nil { 168 | fmt.Printf("❌ Error getting current directory: %v\n", err) 169 | os.Exit(1) 170 | } 171 | dataFile := filepath.Join(filepath.Dir(currentDir), "jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json") 172 | 173 | fmt.Println("📋 Test Parameters:") 174 | fmt.Printf("UUID: %s\n", uuid) 175 | fmt.Printf("Password: %s\n", password) 176 | fmt.Printf("Data File: %s\n", filepath.Base(dataFile)) 177 | 178 | // Read encrypted data 179 | rawData, err := ioutil.ReadFile(dataFile) 180 | if err != nil { 181 | fmt.Printf("❌ Error reading file: %v\n", err) 182 | os.Exit(1) 183 | } 184 | 185 | var data EncryptedFile 186 | err = json.Unmarshal(rawData, &data) 187 | if err != nil { 188 | fmt.Printf("❌ Error parsing JSON: %v\n", err) 189 | os.Exit(1) 190 | } 191 | 192 | fmt.Printf("\n🔐 Encrypted Data Length: %d characters\n", len(data.Encrypted)) 193 | 194 | encryptedPreview := data.Encrypted 195 | if len(encryptedPreview) > 50 { 196 | encryptedPreview = encryptedPreview[:50] + "..." 197 | } 198 | fmt.Printf("Encrypted Data (first 50 chars): %s\n", encryptedPreview) 199 | 200 | // Decrypt 201 | fmt.Println("\n🔓 Decrypting...") 202 | decrypted, err := Decrypt(uuid, data.Encrypted, password) 203 | if err != nil { 204 | fmt.Printf("❌ Decryption failed: %v\n", err) 205 | os.Exit(1) 206 | } 207 | 208 | fmt.Println("✅ Decryption successful!") 209 | fmt.Println("\n📊 Decrypted Data Summary:") 210 | fmt.Printf("- Cookie domains: %d\n", len(decrypted.CookieData)) 211 | fmt.Printf("- Local storage domains: %d\n", len(decrypted.LocalStorageData)) 212 | fmt.Printf("- Update time: %s\n", decrypted.UpdateTime) 213 | 214 | // Print detailed data 215 | if len(decrypted.CookieData) > 0 { 216 | printCookies(decrypted.CookieData) 217 | } 218 | 219 | if len(decrypted.LocalStorageData) > 0 { 220 | printLocalStorage(decrypted.LocalStorageData) 221 | } 222 | 223 | fmt.Println("\n🎉 Go decryption completed successfully!") 224 | } 225 | -------------------------------------------------------------------------------- /examples/fixediv/go/go.mod: -------------------------------------------------------------------------------- 1 | module cookiecloud-decrypt-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /examples/fixediv/java-simple/DecryptSimple.java: -------------------------------------------------------------------------------- 1 | /** 2 | * CookieCloud Fixed IV Decryption - Java Simple Implementation 3 | * 4 | * A minimal Java implementation without external dependencies. 5 | * Uses only standard JDK libraries for maximum compatibility. 6 | * 7 | * Compile: javac DecryptSimple.java 8 | * Run: java DecryptSimple 9 | */ 10 | 11 | import javax.crypto.Cipher; 12 | import javax.crypto.spec.SecretKeySpec; 13 | import javax.crypto.spec.IvParameterSpec; 14 | import java.security.MessageDigest; 15 | import java.util.Base64; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.nio.charset.StandardCharsets; 19 | 20 | public class DecryptSimple { 21 | 22 | /** 23 | * Decrypt CookieCloud data using standard Java crypto 24 | */ 25 | public static String decrypt(String uuid, String encrypted, String password) throws Exception { 26 | // 1. Generate key: MD5(uuid + "-" + password) first 16 chars 27 | String keyInput = uuid + "-" + password; 28 | MessageDigest md = MessageDigest.getInstance("MD5"); 29 | byte[] hashBytes = md.digest(keyInput.getBytes(StandardCharsets.UTF_8)); 30 | 31 | // Convert to hex string 32 | StringBuilder hexString = new StringBuilder(); 33 | for (byte b : hashBytes) { 34 | String hex = Integer.toHexString(0xff & b); 35 | if (hex.length() == 1) hexString.append('0'); 36 | hexString.append(hex); 37 | } 38 | String key = hexString.toString().substring(0, 16); 39 | 40 | // 2. Fixed IV: 16 bytes of zeros 41 | byte[] iv = new byte[16]; 42 | 43 | // 3. Decode base64 encrypted data 44 | byte[] encryptedData = Base64.getDecoder().decode(encrypted); 45 | 46 | // 4. Create AES-128-CBC cipher 47 | SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 48 | IvParameterSpec ivSpec = new IvParameterSpec(iv); 49 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 50 | cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); 51 | 52 | // 5. Decrypt 53 | byte[] decrypted = cipher.doFinal(encryptedData); 54 | 55 | return new String(decrypted, StandardCharsets.UTF_8); 56 | } 57 | 58 | /** 59 | * Simple JSON value extractor (without external libraries) 60 | */ 61 | public static String extractJsonValue(String json, String key) { 62 | String searchKey = "\"" + key + "\":"; 63 | int startIndex = json.indexOf(searchKey); 64 | if (startIndex == -1) return null; 65 | 66 | startIndex += searchKey.length(); 67 | // Skip whitespace 68 | while (startIndex < json.length() && Character.isWhitespace(json.charAt(startIndex))) { 69 | startIndex++; 70 | } 71 | 72 | if (startIndex >= json.length()) return null; 73 | 74 | char firstChar = json.charAt(startIndex); 75 | if (firstChar == '"') { 76 | // String value 77 | startIndex++; // Skip opening quote 78 | int endIndex = json.indexOf('"', startIndex); 79 | while (endIndex != -1 && json.charAt(endIndex - 1) == '\\') { 80 | endIndex = json.indexOf('"', endIndex + 1); 81 | } 82 | if (endIndex == -1) return null; 83 | return json.substring(startIndex, endIndex); 84 | } else if (firstChar == '{' || firstChar == '[') { 85 | // Object or array - find matching closing bracket 86 | char openChar = firstChar; 87 | char closeChar = (openChar == '{') ? '}' : ']'; 88 | int depth = 1; 89 | int pos = startIndex + 1; 90 | 91 | while (pos < json.length() && depth > 0) { 92 | char c = json.charAt(pos); 93 | if (c == openChar) depth++; 94 | else if (c == closeChar) depth--; 95 | pos++; 96 | } 97 | 98 | if (depth == 0) { 99 | return json.substring(startIndex, pos); 100 | } 101 | } else { 102 | // Number, boolean, or null 103 | int endIndex = startIndex; 104 | while (endIndex < json.length() && 105 | !Character.isWhitespace(json.charAt(endIndex)) && 106 | json.charAt(endIndex) != ',' && 107 | json.charAt(endIndex) != '}' && 108 | json.charAt(endIndex) != ']') { 109 | endIndex++; 110 | } 111 | return json.substring(startIndex, endIndex); 112 | } 113 | 114 | return null; 115 | } 116 | 117 | /** 118 | * Count JSON object keys 119 | */ 120 | public static int countJsonKeys(String jsonObject) { 121 | if (jsonObject == null || !jsonObject.trim().startsWith("{")) return 0; 122 | 123 | int count = 0; 124 | int pos = 1; // Skip opening brace 125 | boolean inString = false; 126 | boolean escaped = false; 127 | 128 | while (pos < jsonObject.length()) { 129 | char c = jsonObject.charAt(pos); 130 | 131 | if (escaped) { 132 | escaped = false; 133 | } else if (c == '\\') { 134 | escaped = true; 135 | } else if (c == '"') { 136 | inString = !inString; 137 | } else if (!inString && c == ':') { 138 | count++; 139 | } 140 | 141 | pos++; 142 | } 143 | 144 | return count; 145 | } 146 | 147 | public static void main(String[] args) { 148 | System.out.println("=== CookieCloud Fixed IV Decryption - Java Simple ===\n"); 149 | 150 | // Test parameters 151 | String uuid = "jNp1T2qZ6shwVW9VmjLvp1"; 152 | String password = "iZ4PCqzfJcHyiwAQcCuupD"; 153 | String dataFile = "../jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json"; 154 | 155 | System.out.println("📋 Test Parameters:"); 156 | System.out.println("UUID: " + uuid); 157 | System.out.println("Password: " + password); 158 | System.out.println("Data File: " + dataFile.substring(dataFile.lastIndexOf('/') + 1)); 159 | 160 | try { 161 | // Read encrypted data 162 | String rawData = Files.readString(Paths.get(dataFile), StandardCharsets.UTF_8); 163 | String encryptedData = extractJsonValue(rawData, "encrypted"); 164 | 165 | if (encryptedData == null) { 166 | throw new Exception("Could not extract encrypted data from JSON"); 167 | } 168 | 169 | System.out.println("\n🔐 Encrypted Data Length: " + encryptedData.length() + " characters"); 170 | System.out.println("Encrypted Data (first 50 chars): " + 171 | encryptedData.substring(0, Math.min(50, encryptedData.length())) + "..."); 172 | 173 | // Decrypt 174 | System.out.println("\n🔓 Decrypting..."); 175 | String decryptedJson = decrypt(uuid, encryptedData, password); 176 | 177 | System.out.println("✅ Decryption successful!"); 178 | System.out.println("\n📊 Decrypted Data Summary:"); 179 | 180 | // Extract and analyze data without external JSON library 181 | String cookieData = extractJsonValue(decryptedJson, "cookie_data"); 182 | String localStorageData = extractJsonValue(decryptedJson, "local_storage_data"); 183 | String updateTime = extractJsonValue(decryptedJson, "update_time"); 184 | 185 | int cookieDomains = countJsonKeys(cookieData); 186 | int localStorageDomains = countJsonKeys(localStorageData); 187 | 188 | System.out.println("- Cookie domains: " + cookieDomains); 189 | System.out.println("- Local storage domains: " + localStorageDomains); 190 | System.out.println("- Update time: " + updateTime); 191 | 192 | // Show first 200 characters of decrypted data 193 | System.out.println("\n📄 Decrypted Data Preview:"); 194 | String preview = decryptedJson.length() > 200 ? 195 | decryptedJson.substring(0, 200) + "..." : decryptedJson; 196 | System.out.println(preview); 197 | 198 | System.out.println("\n🎉 Java Simple decryption completed successfully!"); 199 | System.out.println("\n💡 This implementation uses only standard JDK libraries!"); 200 | System.out.println(" No external dependencies like Jackson required."); 201 | 202 | } catch (Exception e) { 203 | System.err.println("❌ Decryption failed: " + e.getMessage()); 204 | e.printStackTrace(); 205 | System.exit(1); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /examples/fixediv/java-simple/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - Java Simple 2 | 3 | This is an ultra-minimal Java implementation that uses **ONLY standard JDK libraries**. No external dependencies like Jackson or Maven required! 4 | 5 | ## 🚀 Key Features 6 | 7 | - ✅ **Zero Dependencies** - Uses only standard JDK libraries 8 | - ✅ **Single File** - Everything in one `.java` file 9 | - ✅ **JDK 8+ Compatible** - Works with any modern Java version 10 | - ✅ **No Build Tools** - Just compile and run with `javac` and `java` 11 | - ✅ **Minimal JSON Parsing** - Custom lightweight JSON extractor 12 | 13 | ## Requirements 14 | 15 | - Java 8 or higher 16 | - Nothing else! 17 | 18 | ## Usage 19 | 20 | ### Quick Start 21 | 22 | ```bash 23 | # Compile 24 | javac DecryptSimple.java 25 | 26 | # Run 27 | java DecryptSimple 28 | ``` 29 | 30 | That's it! No Maven, no Gradle, no external JARs needed. 31 | 32 | ### As Library 33 | 34 | ```java 35 | // Use the decrypt method in your code 36 | String decryptedJson = DecryptSimple.decrypt(uuid, encrypted, password); 37 | ``` 38 | 39 | ### Integration Example 40 | 41 | ```java 42 | import java.util.Scanner; 43 | 44 | public class MyApp { 45 | public static void main(String[] args) { 46 | Scanner scanner = new Scanner(System.in); 47 | 48 | System.out.print("Enter UUID: "); 49 | String uuid = scanner.nextLine(); 50 | 51 | System.out.print("Enter encrypted data: "); 52 | String encrypted = scanner.nextLine(); 53 | 54 | System.out.print("Enter password: "); 55 | String password = scanner.nextLine(); 56 | 57 | try { 58 | String result = DecryptSimple.decrypt(uuid, encrypted, password); 59 | System.out.println("Decrypted: " + result); 60 | } catch (Exception e) { 61 | System.err.println("Error: " + e.getMessage()); 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | ## Algorithm Details 68 | 69 | - **Algorithm**: AES-128-CBC 70 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 71 | - **IV**: Fixed 16 bytes of zeros 72 | - **Padding**: PKCS5/PKCS7 (Java's PKCS5Padding is equivalent) 73 | - **Encoding**: Base64 74 | 75 | ## What's Different from the Maven Version? 76 | 77 | | Feature | Maven Version | Simple Version | 78 | |---------|---------------|----------------| 79 | | Dependencies | Jackson, Maven | None | 80 | | JSON Parsing | Full Jackson library | Lightweight custom parser | 81 | | Build System | Maven/Gradle required | Just javac | 82 | | File Size | ~1MB+ with deps | Single .java file | 83 | | Complexity | Professional setup | Minimal and educational | 84 | 85 | ## Limitations 86 | 87 | 1. **JSON Parsing**: Basic extraction only, not full JSON parsing 88 | 2. **Error Handling**: Simplified compared to full implementation 89 | 3. **Features**: Focused on core decryption functionality 90 | 4. **Performance**: Slightly slower than optimized libraries 91 | 92 | ## When to Use 93 | 94 | ✅ **Use Simple Version When:** 95 | - Learning or prototyping 96 | - Minimal dependencies required 97 | - Educational purposes 98 | - Quick integration into existing projects 99 | - No build system available 100 | 101 | ✅ **Use Maven Version When:** 102 | - Production applications 103 | - Full JSON manipulation needed 104 | - Professional development environment 105 | - Complex error handling required 106 | 107 | ## Deployment 108 | 109 | ### Standalone JAR 110 | 111 | ```bash 112 | # Compile 113 | javac DecryptSimple.java 114 | 115 | # Create JAR 116 | jar cfe decrypt-simple.jar DecryptSimple DecryptSimple.class 117 | 118 | # Run JAR 119 | java -jar decrypt-simple.jar 120 | ``` 121 | 122 | ### Docker 123 | 124 | ```dockerfile 125 | FROM openjdk:8-jre-alpine 126 | 127 | WORKDIR /app 128 | COPY DecryptSimple.java . 129 | COPY ../jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json . 130 | 131 | RUN javac DecryptSimple.java 132 | 133 | CMD ["java", "DecryptSimple"] 134 | ``` 135 | 136 | ### Web Server 137 | 138 | ```java 139 | // Simple HTTP server (Java 8+) 140 | import com.sun.net.httpserver.HttpServer; 141 | import com.sun.net.httpserver.HttpHandler; 142 | import com.sun.net.httpserver.HttpExchange; 143 | import java.net.InetSocketAddress; 144 | import java.io.IOException; 145 | import java.io.OutputStream; 146 | 147 | public class DecryptServer { 148 | public static void main(String[] args) throws IOException { 149 | HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); 150 | 151 | server.createContext("/decrypt", new HttpHandler() { 152 | @Override 153 | public void handle(HttpExchange exchange) throws IOException { 154 | if ("POST".equals(exchange.getRequestMethod())) { 155 | // Handle decrypt request 156 | // Parse JSON from request body 157 | // Call DecryptSimple.decrypt() 158 | // Return JSON response 159 | } else { 160 | exchange.sendResponseHeaders(405, 0); 161 | } 162 | exchange.close(); 163 | } 164 | }); 165 | 166 | server.start(); 167 | System.out.println("Server started on port 8080"); 168 | } 169 | } 170 | ``` 171 | 172 | ## Testing 173 | 174 | ```bash 175 | # Test compilation 176 | javac DecryptSimple.java && echo "✅ Compilation successful" 177 | 178 | # Test execution 179 | java DecryptSimple && echo "✅ Execution successful" 180 | 181 | # Clean up 182 | rm *.class 183 | ``` 184 | 185 | ## Performance 186 | 187 | - **Decryption time**: ~5-15ms (slightly slower than Jackson version) 188 | - **Memory usage**: ~10-20MB (much less than Maven version) 189 | - **Startup time**: ~100ms (faster than Maven version) 190 | - **File size**: Single .java file (~8KB) 191 | 192 | ## Security Notes 193 | 194 | This implementation maintains the same cryptographic security as the full version: 195 | 196 | - Uses standard JDK crypto libraries 197 | - Proper AES-128-CBC implementation 198 | - Secure key derivation 199 | - Safe padding handling 200 | 201 | ## Troubleshooting 202 | 203 | ### Compilation Issues 204 | 205 | ```bash 206 | # Check Java version 207 | java -version 208 | 209 | # Ensure JAVA_HOME is set 210 | echo $JAVA_HOME 211 | 212 | # Compile with verbose output 213 | javac -verbose DecryptSimple.java 214 | ``` 215 | 216 | ### Runtime Issues 217 | 218 | ```bash 219 | # Run with debug output 220 | java -Djavax.crypto.debug=all DecryptSimple 221 | 222 | # Check available crypto providers 223 | java -cp . -Djava.security.debug=provider DecryptSimple 224 | ``` 225 | 226 | ### Common Errors 227 | 228 | 1. **NoSuchAlgorithmException**: JDK crypto policy issue 229 | - Solution: Update to newer JDK or install unlimited strength crypto 230 | 231 | 2. **ClassNotFoundException**: Compilation issue 232 | - Solution: Ensure .class file exists in same directory 233 | 234 | 3. **JSON parsing errors**: Malformed input 235 | - Solution: Validate input JSON format 236 | 237 | ## License 238 | 239 | Same as main project - provided for educational and integration purposes. 240 | -------------------------------------------------------------------------------- /examples/fixediv/java/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - Java 2 | 3 | This is a Java implementation for decrypting CookieCloud's `aes-128-cbc-fixed` encrypted data using standard Java cryptography libraries. 4 | 5 | ## Requirements 6 | 7 | - Java 8 or higher 8 | - Maven 3.6+ (for building) 9 | - Jackson library (for JSON parsing) 10 | 11 | ## Installation 12 | 13 | ### Using Maven 14 | 15 | ```bash 16 | cd java 17 | mvn clean install 18 | ``` 19 | 20 | ### Using Gradle (alternative) 21 | 22 | Create `build.gradle`: 23 | 24 | ```gradle 25 | plugins { 26 | id 'java' 27 | id 'application' 28 | } 29 | 30 | java { 31 | sourceCompatibility = JavaVersion.VERSION_1_8 32 | targetCompatibility = JavaVersion.VERSION_1_8 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | } 38 | 39 | dependencies { 40 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' 41 | testImplementation 'junit:junit:4.13.2' 42 | } 43 | 44 | application { 45 | mainClass = 'com.cookiecloud.decrypt.DecryptMain' 46 | } 47 | 48 | jar { 49 | manifest { 50 | attributes 'Main-Class': 'com.cookiecloud.decrypt.DecryptMain' 51 | } 52 | from { 53 | configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } 54 | } 55 | } 56 | ``` 57 | 58 | ## Usage 59 | 60 | ### Command Line (Maven) 61 | 62 | ```bash 63 | mvn exec:java -Dexec.mainClass="com.cookiecloud.decrypt.DecryptMain" 64 | ``` 65 | 66 | ### Command Line (JAR) 67 | 68 | ```bash 69 | # Build fat JAR 70 | mvn clean package 71 | 72 | # Run 73 | java -jar target/decrypt-java-1.0.0.jar 74 | ``` 75 | 76 | ### As Library 77 | 78 | ```java 79 | import com.cookiecloud.decrypt.DecryptMain; 80 | 81 | public class Example { 82 | public static void main(String[] args) { 83 | String uuid = "your-uuid"; 84 | String encrypted = "base64-encrypted-data"; 85 | String password = "your-password"; 86 | 87 | try { 88 | String decryptedJson = DecryptMain.decrypt(uuid, encrypted, password); 89 | System.out.println("Decrypted: " + decryptedJson); 90 | } catch (Exception e) { 91 | System.err.println("Decryption failed: " + e.getMessage()); 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ### Spring Boot Integration 98 | 99 | ```java 100 | @RestController 101 | @RequestMapping("/api") 102 | public class DecryptController { 103 | 104 | @PostMapping("/decrypt") 105 | public ResponseEntity decrypt(@RequestBody DecryptRequest request) { 106 | try { 107 | String result = DecryptMain.decrypt( 108 | request.getUuid(), 109 | request.getEncrypted(), 110 | request.getPassword() 111 | ); 112 | 113 | ObjectMapper mapper = new ObjectMapper(); 114 | JsonNode data = mapper.readTree(result); 115 | 116 | return ResponseEntity.ok(new DecryptResponse(true, data, null)); 117 | } catch (Exception e) { 118 | return ResponseEntity.badRequest() 119 | .body(new DecryptResponse(false, null, e.getMessage())); 120 | } 121 | } 122 | } 123 | 124 | // Request/Response classes 125 | public class DecryptRequest { 126 | private String uuid; 127 | private String encrypted; 128 | private String password; 129 | 130 | // getters and setters 131 | } 132 | 133 | public class DecryptResponse { 134 | private boolean success; 135 | private JsonNode data; 136 | private String error; 137 | 138 | // constructors, getters and setters 139 | } 140 | ``` 141 | 142 | ### Servlet Integration 143 | 144 | ```java 145 | @WebServlet("/decrypt") 146 | public class DecryptServlet extends HttpServlet { 147 | private ObjectMapper mapper = new ObjectMapper(); 148 | 149 | @Override 150 | protected void doPost(HttpServletRequest request, HttpServletResponse response) 151 | throws ServletException, IOException { 152 | 153 | response.setContentType("application/json"); 154 | response.setCharacterEncoding("UTF-8"); 155 | 156 | try { 157 | // Parse request 158 | JsonNode requestData = mapper.readTree(request.getInputStream()); 159 | String uuid = requestData.get("uuid").asText(); 160 | String encrypted = requestData.get("encrypted").asText(); 161 | String password = requestData.get("password").asText(); 162 | 163 | // Decrypt 164 | String decryptedJson = DecryptMain.decrypt(uuid, encrypted, password); 165 | JsonNode decrypted = mapper.readTree(decryptedJson); 166 | 167 | // Response 168 | ObjectNode responseObj = mapper.createObjectNode(); 169 | responseObj.put("success", true); 170 | responseObj.set("data", decrypted); 171 | 172 | response.getWriter().write(responseObj.toString()); 173 | 174 | } catch (Exception e) { 175 | ObjectNode errorResponse = mapper.createObjectNode(); 176 | errorResponse.put("success", false); 177 | errorResponse.put("error", e.getMessage()); 178 | 179 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST); 180 | response.getWriter().write(errorResponse.toString()); 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | ## Building 187 | 188 | ### Maven Commands 189 | 190 | ```bash 191 | # Compile 192 | mvn compile 193 | 194 | # Test 195 | mvn test 196 | 197 | # Package (creates fat JAR) 198 | mvn package 199 | 200 | # Install to local repository 201 | mvn install 202 | 203 | # Clean 204 | mvn clean 205 | ``` 206 | 207 | ### IDE Setup 208 | 209 | **IntelliJ IDEA:** 210 | 1. Import as Maven project 211 | 2. Set Project SDK to Java 8+ 212 | 3. Run `DecryptMain.main()` 213 | 214 | **Eclipse:** 215 | 1. Import → Existing Maven Projects 216 | 2. Select the java directory 217 | 3. Run as Java Application 218 | 219 | **VS Code:** 220 | 1. Install Java Extension Pack 221 | 2. Open java directory 222 | 3. Run via CodeLens or Command Palette 223 | 224 | ## Algorithm Details 225 | 226 | - **Algorithm**: AES-128-CBC 227 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 228 | - **IV**: Fixed 16 bytes of zeros 229 | - **Padding**: PKCS5/PKCS7 (Java uses PKCS5Padding which is equivalent to PKCS7) 230 | - **Encoding**: Base64 231 | 232 | ## Performance 233 | 234 | - Decryption time: ~5-10ms for typical CookieCloud data 235 | - Memory usage: ~20-30MB (including JVM overhead) 236 | - JAR size: ~1MB (with dependencies) 237 | 238 | ## Dependencies 239 | 240 | - **Jackson Databind**: JSON parsing and generation 241 | - Core library for JSON operations 242 | - Handles complex nested structures 243 | - Thread-safe for concurrent operations 244 | 245 | ## Testing 246 | 247 | Create `src/test/java/com/cookiecloud/decrypt/DecryptTest.java`: 248 | 249 | ```java 250 | import org.junit.Test; 251 | import static org.junit.Assert.*; 252 | 253 | public class DecryptTest { 254 | 255 | @Test 256 | public void testDecrypt() throws Exception { 257 | String uuid = "test-uuid"; 258 | String password = "test-password"; 259 | // Add test encrypted data here 260 | String encrypted = "..."; 261 | 262 | String result = DecryptMain.decrypt(uuid, encrypted, password); 263 | assertNotNull(result); 264 | assertTrue(result.contains("cookie_data")); 265 | } 266 | } 267 | ``` 268 | 269 | Run tests: 270 | ```bash 271 | mvn test 272 | ``` 273 | 274 | ## Docker 275 | 276 | Create `Dockerfile`: 277 | 278 | ```dockerfile 279 | FROM openjdk:8-jre-alpine 280 | 281 | WORKDIR /app 282 | 283 | # Copy JAR file 284 | COPY target/decrypt-java-1.0.0.jar app.jar 285 | 286 | # Copy test data 287 | COPY ../jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json . 288 | 289 | # Run 290 | CMD ["java", "-jar", "app.jar"] 291 | ``` 292 | 293 | Build and run: 294 | ```bash 295 | mvn clean package 296 | docker build -t cookiecloud-decrypt-java . 297 | docker run --rm cookiecloud-decrypt-java 298 | ``` 299 | 300 | ## Troubleshooting 301 | 302 | ### Common Issues 303 | 304 | 1. **ClassNotFoundException**: Make sure Jackson is in classpath 305 | 2. **NoSuchAlgorithmException**: Ensure JVM supports AES/CBC/PKCS5Padding 306 | 3. **OutOfMemoryError**: Increase heap size with `-Xmx512m` 307 | 308 | ### Debug Mode 309 | 310 | Add JVM arguments: 311 | ```bash 312 | java -Djavax.crypto.debug=all -jar target/decrypt-java-1.0.0.jar 313 | ``` 314 | -------------------------------------------------------------------------------- /examples/fixediv/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.cookiecloud 9 | decrypt-java 10 | 1.0.0 11 | jar 12 | 13 | CookieCloud Decrypt Java 14 | CookieCloud Fixed IV Decryption - Java Implementation 15 | 16 | 17 | 8 18 | 8 19 | UTF-8 20 | 2.15.2 21 | 22 | 23 | 24 | 25 | com.fasterxml.jackson.core 26 | jackson-databind 27 | ${jackson.version} 28 | 29 | 30 | 31 | 32 | junit 33 | junit 34 | 4.13.2 35 | test 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.11.0 45 | 46 | 8 47 | 8 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-shade-plugin 54 | 3.4.1 55 | 56 | 57 | package 58 | 59 | shade 60 | 61 | 62 | 63 | 64 | com.cookiecloud.decrypt.DecryptMain 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/fixediv/java/src/main/java/com/cookiecloud/decrypt/DecryptMain.java: -------------------------------------------------------------------------------- 1 | package com.cookiecloud.decrypt; 2 | 3 | /** 4 | * CookieCloud Fixed IV Decryption - Java Implementation 5 | * 6 | * Algorithm: AES-128-CBC 7 | * Key: MD5(uuid + "-" + password).substring(0, 16) 8 | * IV: Fixed 16 bytes of zeros (0x00000000000000000000000000000000) 9 | * Padding: PKCS7 10 | * Encoding: Base64 11 | */ 12 | 13 | import javax.crypto.Cipher; 14 | import javax.crypto.spec.SecretKeySpec; 15 | import javax.crypto.spec.IvParameterSpec; 16 | import java.security.MessageDigest; 17 | import java.util.Base64; 18 | import java.nio.file.Files; 19 | import java.nio.file.Paths; 20 | import java.nio.charset.StandardCharsets; 21 | import com.fasterxml.jackson.databind.ObjectMapper; 22 | import com.fasterxml.jackson.databind.JsonNode; 23 | import java.util.Iterator; 24 | import java.util.Map; 25 | import java.io.File; 26 | 27 | public class DecryptMain { 28 | 29 | /** 30 | * Decrypt CookieCloud data using standard Java crypto 31 | * 32 | * @param uuid User UUID 33 | * @param encrypted Base64 encrypted data 34 | * @param password Password 35 | * @return Decrypted JSON string 36 | * @throws Exception if decryption fails 37 | */ 38 | public static String decrypt(String uuid, String encrypted, String password) throws Exception { 39 | // 1. Generate key: first 16 characters of MD5(uuid + "-" + password) 40 | String keyInput = uuid + "-" + password; 41 | MessageDigest md = MessageDigest.getInstance("MD5"); 42 | byte[] hashBytes = md.digest(keyInput.getBytes(StandardCharsets.UTF_8)); 43 | 44 | // Convert to hex string 45 | StringBuilder hexString = new StringBuilder(); 46 | for (byte b : hashBytes) { 47 | String hex = Integer.toHexString(0xff & b); 48 | if (hex.length() == 1) { 49 | hexString.append('0'); 50 | } 51 | hexString.append(hex); 52 | } 53 | 54 | String key = hexString.toString().substring(0, 16); 55 | 56 | // 2. Fixed IV: 16 bytes of zeros 57 | byte[] iv = new byte[16]; // Default to all zeros 58 | 59 | // 3. Decode base64 encrypted data 60 | byte[] encryptedData = Base64.getDecoder().decode(encrypted); 61 | 62 | // 4. Create AES-128-CBC cipher 63 | SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 64 | IvParameterSpec ivSpec = new IvParameterSpec(iv); 65 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 66 | cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); 67 | 68 | // 5. Decrypt 69 | byte[] decrypted = cipher.doFinal(encryptedData); 70 | 71 | // 6. Convert to string 72 | return new String(decrypted, StandardCharsets.UTF_8); 73 | } 74 | 75 | /** 76 | * Pretty print cookie data 77 | */ 78 | public static void printCookies(JsonNode cookieData) { 79 | System.out.println("\n🍪 Cookie Data:"); 80 | 81 | Iterator> domains = cookieData.fields(); 82 | while (domains.hasNext()) { 83 | Map.Entry domain = domains.next(); 84 | System.out.println("\n📍 " + domain.getKey() + ":"); 85 | 86 | JsonNode cookies = domain.getValue(); 87 | for (int i = 0; i < cookies.size(); i++) { 88 | JsonNode cookie = cookies.get(i); 89 | System.out.println(" " + (i + 1) + ". " + 90 | cookie.get("name").asText() + " = " + cookie.get("value").asText()); 91 | 92 | if (cookie.has("path") && !cookie.get("path").isNull()) { 93 | System.out.println(" Path: " + cookie.get("path").asText()); 94 | } 95 | if (cookie.has("secure") && cookie.get("secure").asBoolean()) { 96 | System.out.println(" Secure: " + cookie.get("secure").asBoolean()); 97 | } 98 | if (cookie.has("httpOnly") && cookie.get("httpOnly").asBoolean()) { 99 | System.out.println(" HttpOnly: " + cookie.get("httpOnly").asBoolean()); 100 | } 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * Pretty print local storage data 107 | */ 108 | public static void printLocalStorage(JsonNode localStorageData) { 109 | System.out.println("\n💾 Local Storage Data:"); 110 | 111 | Iterator> domains = localStorageData.fields(); 112 | while (domains.hasNext()) { 113 | Map.Entry domain = domains.next(); 114 | System.out.println("\n📍 " + domain.getKey() + ":"); 115 | 116 | JsonNode storage = domain.getValue(); 117 | Iterator> entries = storage.fields(); 118 | while (entries.hasNext()) { 119 | Map.Entry entry = entries.next(); 120 | String value = entry.getValue().asText(); 121 | String displayValue = value.length() > 50 ? value.substring(0, 50) + "..." : value; 122 | System.out.println(" " + entry.getKey() + " = " + displayValue); 123 | } 124 | } 125 | } 126 | 127 | public static void main(String[] args) { 128 | System.out.println("=== CookieCloud Fixed IV Decryption - Java ===\n"); 129 | 130 | // Test parameters 131 | String uuid = "jNp1T2qZ6shwVW9VmjLvp1"; 132 | String password = "iZ4PCqzfJcHyiwAQcCuupD"; 133 | 134 | // Get data file path (two directories up from src/main/java) 135 | File currentDir = new File(System.getProperty("user.dir")); 136 | File dataFile = new File(currentDir.getParentFile(), "jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json"); 137 | 138 | System.out.println("📋 Test Parameters:"); 139 | System.out.println("UUID: " + uuid); 140 | System.out.println("Password: " + password); 141 | System.out.println("Data File: " + dataFile.getName()); 142 | 143 | try { 144 | // Read encrypted data 145 | String rawData = Files.readString(dataFile.toPath(), StandardCharsets.UTF_8); 146 | ObjectMapper mapper = new ObjectMapper(); 147 | JsonNode data = mapper.readTree(rawData); 148 | 149 | String encryptedData = data.get("encrypted").asText(); 150 | System.out.println("\n🔐 Encrypted Data Length: " + encryptedData.length() + " characters"); 151 | System.out.println("Encrypted Data (first 50 chars): " + encryptedData.substring(0, 50) + "..."); 152 | 153 | // Decrypt 154 | System.out.println("\n🔓 Decrypting..."); 155 | String decryptedJson = decrypt(uuid, encryptedData, password); 156 | JsonNode decrypted = mapper.readTree(decryptedJson); 157 | 158 | System.out.println("✅ Decryption successful!"); 159 | System.out.println("\n📊 Decrypted Data Summary:"); 160 | 161 | JsonNode cookieData = decrypted.get("cookie_data"); 162 | JsonNode localStorageData = decrypted.get("local_storage_data"); 163 | 164 | System.out.println("- Cookie domains: " + (cookieData != null ? cookieData.size() : 0)); 165 | System.out.println("- Local storage domains: " + (localStorageData != null ? localStorageData.size() : 0)); 166 | System.out.println("- Update time: " + decrypted.get("update_time").asText()); 167 | 168 | // Print detailed data 169 | if (cookieData != null && cookieData.size() > 0) { 170 | printCookies(cookieData); 171 | } 172 | 173 | if (localStorageData != null && localStorageData.size() > 0) { 174 | printLocalStorage(localStorageData); 175 | } 176 | 177 | System.out.println("\n🎉 Java decryption completed successfully!"); 178 | 179 | } catch (Exception e) { 180 | System.err.println("❌ Decryption failed: " + e.getMessage()); 181 | e.printStackTrace(); 182 | System.exit(1); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /examples/fixediv/nodejs/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - Node.js 2 | 3 | This is a Node.js implementation for decrypting CookieCloud's `aes-128-cbc-fixed` encrypted data using standard Node.js crypto library. 4 | 5 | ## Requirements 6 | 7 | - Node.js 12.0.0 or higher 8 | - No external dependencies (uses built-in crypto module) 9 | 10 | ## Installation 11 | 12 | ```bash 13 | cd nodejs 14 | npm install 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Command Line 20 | 21 | ```bash 22 | npm start 23 | # or 24 | node decrypt.js 25 | ``` 26 | 27 | ### As Module 28 | 29 | ```javascript 30 | const { decrypt } = require('./decrypt.js'); 31 | 32 | const uuid = 'your-uuid'; 33 | const encrypted = 'base64-encrypted-data'; 34 | const password = 'your-password'; 35 | 36 | try { 37 | const data = decrypt(uuid, encrypted, password); 38 | console.log('Decrypted data:', data); 39 | } catch (error) { 40 | console.error('Decryption failed:', error.message); 41 | } 42 | ``` 43 | 44 | ### API Integration 45 | 46 | ```javascript 47 | const express = require('express'); 48 | const { decrypt } = require('./decrypt.js'); 49 | 50 | const app = express(); 51 | app.use(express.json()); 52 | 53 | app.post('/decrypt', (req, res) => { 54 | const { uuid, encrypted, password } = req.body; 55 | 56 | try { 57 | const data = decrypt(uuid, encrypted, password); 58 | res.json({ success: true, data }); 59 | } catch (error) { 60 | res.status(400).json({ success: false, error: error.message }); 61 | } 62 | }); 63 | 64 | app.listen(3000, () => { 65 | console.log('CookieCloud decrypt API running on port 3000'); 66 | }); 67 | ``` 68 | 69 | ## Algorithm Details 70 | 71 | - **Algorithm**: AES-128-CBC 72 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 73 | - **IV**: Fixed 16 bytes of zeros 74 | - **Padding**: PKCS7 75 | - **Encoding**: Base64 76 | 77 | ## Performance 78 | 79 | - Decryption time: ~1-2ms for typical CookieCloud data 80 | - Memory usage: ~5-10MB 81 | - No external dependencies required 82 | -------------------------------------------------------------------------------- /examples/fixediv/nodejs/decrypt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * CookieCloud Fixed IV Decryption - Node.js Implementation 5 | * 6 | * Algorithm: AES-128-CBC 7 | * Key: MD5(uuid + "-" + password).substring(0, 16) 8 | * IV: Fixed 16 bytes of zeros (0x00000000000000000000000000000000) 9 | * Padding: PKCS7 10 | * Encoding: Base64 11 | */ 12 | 13 | const crypto = require('crypto'); 14 | const fs = require('fs'); 15 | const path = require('path'); 16 | 17 | /** 18 | * Decrypt CookieCloud data using standard Node.js crypto 19 | * @param {string} uuid - User UUID 20 | * @param {string} encrypted - Base64 encrypted data 21 | * @param {string} password - Password 22 | * @returns {object} Decrypted data 23 | */ 24 | function decrypt(uuid, encrypted, password) { 25 | // 1. Generate key: first 16 characters of MD5(uuid + "-" + password) 26 | const keyInput = `${uuid}-${password}`; 27 | const hash = crypto.createHash('md5').update(keyInput).digest('hex'); 28 | const key = Buffer.from(hash.substring(0, 16), 'utf8'); 29 | 30 | // 2. Fixed IV: 16 bytes of zeros 31 | const iv = Buffer.alloc(16, 0); 32 | 33 | // 3. Decode base64 encrypted data 34 | const encryptedBuffer = Buffer.from(encrypted, 'base64'); 35 | 36 | // 4. Create AES-128-CBC decipher 37 | const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); 38 | 39 | // 5. Decrypt and remove padding 40 | let decrypted = decipher.update(encryptedBuffer); 41 | decrypted = Buffer.concat([decrypted, decipher.final()]); 42 | 43 | // 6. Parse JSON 44 | const result = JSON.parse(decrypted.toString('utf8')); 45 | 46 | return result; 47 | } 48 | 49 | /** 50 | * Pretty print cookie data 51 | */ 52 | function printCookies(cookieData) { 53 | console.log('\n🍪 Cookie Data:'); 54 | for (const [domain, cookies] of Object.entries(cookieData)) { 55 | console.log(`\n📍 ${domain}:`); 56 | cookies.forEach((cookie, index) => { 57 | console.log(` ${index + 1}. ${cookie.name} = ${cookie.value}`); 58 | if (cookie.path) console.log(` Path: ${cookie.path}`); 59 | if (cookie.secure) console.log(` Secure: ${cookie.secure}`); 60 | if (cookie.httpOnly) console.log(` HttpOnly: ${cookie.httpOnly}`); 61 | }); 62 | } 63 | } 64 | 65 | /** 66 | * Pretty print local storage data 67 | */ 68 | function printLocalStorage(localStorageData) { 69 | console.log('\n💾 Local Storage Data:'); 70 | for (const [domain, storage] of Object.entries(localStorageData)) { 71 | console.log(`\n📍 ${domain}:`); 72 | for (const [key, value] of Object.entries(storage)) { 73 | const displayValue = value.length > 50 ? value.substring(0, 50) + '...' : value; 74 | console.log(` ${key} = ${displayValue}`); 75 | } 76 | } 77 | } 78 | 79 | function main() { 80 | console.log('=== CookieCloud Fixed IV Decryption - Node.js ===\n'); 81 | 82 | // Test parameters 83 | const uuid = 'jNp1T2qZ6shwVW9VmjLvp1'; 84 | const password = 'iZ4PCqzfJcHyiwAQcCuupD'; 85 | const dataFile = path.join(__dirname, '..', 'jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json'); 86 | 87 | console.log('📋 Test Parameters:'); 88 | console.log(`UUID: ${uuid}`); 89 | console.log(`Password: ${password}`); 90 | console.log(`Data File: ${path.basename(dataFile)}`); 91 | 92 | try { 93 | // Read encrypted data 94 | const rawData = fs.readFileSync(dataFile, 'utf8'); 95 | const data = JSON.parse(rawData); 96 | 97 | console.log(`\n🔐 Encrypted Data Length: ${data.encrypted.length} characters`); 98 | console.log(`Encrypted Data (first 50 chars): ${data.encrypted.substring(0, 50)}...`); 99 | 100 | // Decrypt 101 | console.log('\n🔓 Decrypting...'); 102 | const decrypted = decrypt(uuid, data.encrypted, password); 103 | 104 | console.log('✅ Decryption successful!'); 105 | console.log(`\n📊 Decrypted Data Summary:`); 106 | console.log(`- Cookie domains: ${Object.keys(decrypted.cookie_data || {}).length}`); 107 | console.log(`- Local storage domains: ${Object.keys(decrypted.local_storage_data || {}).length}`); 108 | console.log(`- Update time: ${decrypted.update_time}`); 109 | 110 | // Print detailed data 111 | if (decrypted.cookie_data) { 112 | printCookies(decrypted.cookie_data); 113 | } 114 | 115 | if (decrypted.local_storage_data) { 116 | printLocalStorage(decrypted.local_storage_data); 117 | } 118 | 119 | console.log('\n🎉 Node.js decryption completed successfully!'); 120 | 121 | } catch (error) { 122 | console.error('❌ Decryption failed:', error.message); 123 | process.exit(1); 124 | } 125 | } 126 | 127 | // Export for use as module 128 | module.exports = { decrypt }; 129 | 130 | // Run if called directly 131 | if (require.main === module) { 132 | main(); 133 | } 134 | -------------------------------------------------------------------------------- /examples/fixediv/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookiecloud-decrypt-nodejs", 3 | "version": "1.0.0", 4 | "description": "CookieCloud Fixed IV Decryption - Node.js Implementation", 5 | "main": "decrypt.js", 6 | "scripts": { 7 | "start": "node decrypt.js", 8 | "test": "node test.js" 9 | }, 10 | "keywords": [ 11 | "cookiecloud", 12 | "decrypt", 13 | "aes", 14 | "cbc", 15 | "crypto" 16 | ], 17 | "author": "CookieCloud", 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">=12.0.0" 21 | }, 22 | "dependencies": {}, 23 | "devDependencies": {} 24 | } 25 | -------------------------------------------------------------------------------- /examples/fixediv/php/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - PHP 2 | 3 | This is a PHP implementation for decrypting CookieCloud's `aes-128-cbc-fixed` encrypted data using PHP's OpenSSL functions. 4 | 5 | ## Requirements 6 | 7 | - PHP 7.0 or higher 8 | - OpenSSL extension (usually enabled by default) 9 | - JSON extension (usually enabled by default) 10 | 11 | ## Installation 12 | 13 | ### Using Composer 14 | 15 | ```bash 16 | cd php 17 | composer install 18 | ``` 19 | 20 | ### Manual Installation 21 | 22 | No external dependencies required - uses built-in PHP functions. 23 | 24 | ## Usage 25 | 26 | ### Command Line 27 | 28 | ```bash 29 | php decrypt.php 30 | # or using composer 31 | composer run decrypt 32 | ``` 33 | 34 | ### As Library 35 | 36 | ```php 37 | getMessage() . "\n"; 51 | } 52 | ``` 53 | 54 | ### Web API Integration 55 | 56 | #### Simple API 57 | 58 | ```php 59 | 'Method not allowed']); 68 | exit; 69 | } 70 | 71 | $input = json_decode(file_get_contents('php://input'), true); 72 | 73 | if (!$input || !isset($input['uuid'], $input['encrypted'], $input['password'])) { 74 | http_response_code(400); 75 | echo json_encode(['error' => 'Missing required fields']); 76 | exit; 77 | } 78 | 79 | try { 80 | $data = Decrypt::decrypt($input['uuid'], $input['encrypted'], $input['password']); 81 | echo json_encode(['success' => true, 'data' => $data]); 82 | } catch (Exception $e) { 83 | http_response_code(400); 84 | echo json_encode(['success' => false, 'error' => $e->getMessage()]); 85 | } 86 | ``` 87 | 88 | #### Laravel Integration 89 | 90 | ```php 91 | validate([ 103 | 'uuid' => 'required|string', 104 | 'encrypted' => 'required|string', 105 | 'password' => 'required|string' 106 | ]); 107 | 108 | try { 109 | $data = Decrypt::decrypt( 110 | $request->uuid, 111 | $request->encrypted, 112 | $request->password 113 | ); 114 | 115 | return response()->json([ 116 | 'success' => true, 117 | 'data' => $data 118 | ]); 119 | } catch (\Exception $e) { 120 | return response()->json([ 121 | 'success' => false, 122 | 'error' => $e->getMessage() 123 | ], 400); 124 | } 125 | } 126 | } 127 | 128 | // Route (routes/api.php) 129 | Route::post('/decrypt', [DecryptController::class, 'decrypt']); 130 | ``` 131 | 132 | #### Symfony Integration 133 | 134 | ```php 135 | getContent(), true); 151 | 152 | if (!$data || !isset($data['uuid'], $data['encrypted'], $data['password'])) { 153 | return $this->json(['error' => 'Missing required fields'], 400); 154 | } 155 | 156 | try { 157 | $result = Decrypt::decrypt($data['uuid'], $data['encrypted'], $data['password']); 158 | return $this->json(['success' => true, 'data' => $result]); 159 | } catch (\Exception $e) { 160 | return $this->json(['success' => false, 'error' => $e->getMessage()], 400); 161 | } 162 | } 163 | } 164 | ``` 165 | 166 | #### WordPress Plugin Integration 167 | 168 | ```php 169 | getMessage()); 192 | } 193 | } 194 | ``` 195 | 196 | ## Testing 197 | 198 | ### PHPUnit Tests 199 | 200 | Create `tests/DecryptTest.php`: 201 | 202 | ```php 203 | assertIsArray($result); 222 | $this->assertArrayHasKey('cookie_data', $result); 223 | } 224 | 225 | public function testInvalidBase64() 226 | { 227 | $this->expectException(\Exception::class); 228 | $this->expectExceptionMessage('Base64 decode failed'); 229 | 230 | Decrypt::decrypt('uuid', 'invalid-base64!', 'password'); 231 | } 232 | 233 | public function testOpenSSLSupport() 234 | { 235 | $this->assertTrue(extension_loaded('openssl')); 236 | $this->assertTrue(Decrypt::isAes128CbcSupported()); 237 | } 238 | } 239 | ``` 240 | 241 | Run tests: 242 | ```bash 243 | composer test 244 | # or 245 | ./vendor/bin/phpunit 246 | ``` 247 | 248 | ## Algorithm Details 249 | 250 | - **Algorithm**: AES-128-CBC 251 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 252 | - **IV**: Fixed 16 bytes of zeros 253 | - **Padding**: PKCS7 (manually removed) 254 | - **Encoding**: Base64 255 | 256 | ## Performance 257 | 258 | - Decryption time: ~2-3ms for typical CookieCloud data 259 | - Memory usage: ~5-8MB 260 | - No external dependencies (uses built-in OpenSSL) 261 | 262 | ## Security Considerations 263 | 264 | 1. **Input Validation**: Always validate and sanitize input parameters 265 | 2. **Error Handling**: Don't expose detailed error messages to end users 266 | 3. **Rate Limiting**: Implement rate limiting for API endpoints 267 | 4. **HTTPS**: Always use HTTPS in production 268 | 5. **Memory**: Clear sensitive data from memory after use 269 | 270 | ```php 271 | // Example secure wrapper 272 | function secureDecrypt($uuid, $encrypted, $password) { 273 | // Input validation 274 | if (!is_string($uuid) || !is_string($encrypted) || !is_string($password)) { 275 | throw new InvalidArgumentException('Invalid input types'); 276 | } 277 | 278 | if (strlen($uuid) > 100 || strlen($password) > 100) { 279 | throw new InvalidArgumentException('Input too long'); 280 | } 281 | 282 | try { 283 | return Decrypt::decrypt($uuid, $encrypted, $password); 284 | } finally { 285 | // Clear sensitive variables 286 | $uuid = null; 287 | $password = null; 288 | $encrypted = null; 289 | } 290 | } 291 | ``` 292 | 293 | ## Deployment 294 | 295 | ### Apache 296 | 297 | Create `.htaccess`: 298 | ```apache 299 | RewriteEngine On 300 | RewriteRule ^api/decrypt$ decrypt.php [L] 301 | 302 | # Security headers 303 | Header always set X-Content-Type-Options nosniff 304 | Header always set X-Frame-Options DENY 305 | Header always set X-XSS-Protection "1; mode=block" 306 | ``` 307 | 308 | ### Nginx 309 | 310 | ```nginx 311 | location /api/decrypt { 312 | try_files $uri /decrypt.php; 313 | 314 | # Security headers 315 | add_header X-Content-Type-Options nosniff; 316 | add_header X-Frame-Options DENY; 317 | add_header X-XSS-Protection "1; mode=block"; 318 | } 319 | ``` 320 | 321 | ### Docker 322 | 323 | Create `Dockerfile`: 324 | 325 | ```dockerfile 326 | FROM php:8.1-apache 327 | 328 | # Install extensions 329 | RUN docker-php-ext-install json 330 | 331 | # Copy source code 332 | COPY . /var/www/html/ 333 | 334 | # Set permissions 335 | RUN chown -R www-data:www-data /var/www/html 336 | 337 | # Expose port 338 | EXPOSE 80 339 | 340 | CMD ["apache2-foreground"] 341 | ``` 342 | 343 | Build and run: 344 | ```bash 345 | docker build -t cookiecloud-decrypt-php . 346 | docker run -p 8080:80 cookiecloud-decrypt-php 347 | ``` 348 | -------------------------------------------------------------------------------- /examples/fixediv/php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookiecloud/decrypt-php", 3 | "description": "CookieCloud Fixed IV Decryption - PHP Implementation", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "CookieCloud", 9 | "email": "info@cookiecloud.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.0", 14 | "ext-openssl": "*", 15 | "ext-json": "*" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^8.0|^9.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "CookieCloud\\Decrypt\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "CookieCloud\\Decrypt\\Tests\\": "tests/" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "phpunit", 32 | "decrypt": "php decrypt.php" 33 | }, 34 | "config": { 35 | "optimize-autoloader": true, 36 | "sort-packages": true 37 | }, 38 | "minimum-stability": "stable", 39 | "prefer-stable": true 40 | } 41 | -------------------------------------------------------------------------------- /examples/fixediv/php/decrypt.php: -------------------------------------------------------------------------------- 1 | getMessage() . "\n"; 87 | exit(1); 88 | } 89 | } 90 | 91 | // Run if called directly 92 | if (basename(__FILE__) == basename($_SERVER['SCRIPT_NAME'])) { 93 | main(); 94 | } 95 | -------------------------------------------------------------------------------- /examples/fixediv/php/src/Decrypt.php: -------------------------------------------------------------------------------- 1 | $cookies) { 82 | echo "\n📍 $domain:\n"; 83 | 84 | foreach ($cookies as $index => $cookie) { 85 | $num = $index + 1; 86 | echo " $num. {$cookie['name']} = {$cookie['value']}\n"; 87 | 88 | if (!empty($cookie['path'])) { 89 | echo " Path: {$cookie['path']}\n"; 90 | } 91 | if (!empty($cookie['secure'])) { 92 | echo " Secure: " . ($cookie['secure'] ? 'true' : 'false') . "\n"; 93 | } 94 | if (!empty($cookie['httpOnly'])) { 95 | echo " HttpOnly: " . ($cookie['httpOnly'] ? 'true' : 'false') . "\n"; 96 | } 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Pretty print local storage data 103 | * 104 | * @param array $localStorageData Local storage data array 105 | */ 106 | public static function printLocalStorage($localStorageData) 107 | { 108 | echo "\n💾 Local Storage Data:\n"; 109 | 110 | foreach ($localStorageData as $domain => $storage) { 111 | echo "\n📍 $domain:\n"; 112 | 113 | foreach ($storage as $key => $value) { 114 | $displayValue = strlen($value) > 50 ? substr($value, 0, 50) . '...' : $value; 115 | echo " $key = $displayValue\n"; 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Get supported OpenSSL ciphers 122 | * 123 | * @return array List of supported ciphers 124 | */ 125 | public static function getSupportedCiphers() 126 | { 127 | return openssl_get_cipher_methods(); 128 | } 129 | 130 | /** 131 | * Validate if AES-128-CBC is supported 132 | * 133 | * @return bool True if supported 134 | */ 135 | public static function isAes128CbcSupported() 136 | { 137 | $ciphers = self::getSupportedCiphers(); 138 | // Check for different possible cipher names 139 | return in_array('AES-128-CBC', $ciphers) || 140 | in_array('aes-128-cbc', $ciphers) || 141 | in_array('AES128-CBC', $ciphers); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /examples/fixediv/python/README.md: -------------------------------------------------------------------------------- 1 | # CookieCloud Fixed IV Decryption - Python 2 | 3 | This is a Python implementation for decrypting CookieCloud's `aes-128-cbc-fixed` encrypted data using PyCryptodome library. 4 | 5 | ## Requirements 6 | 7 | - Python 3.6 or higher 8 | - PyCryptodome library 9 | 10 | ## Installation 11 | 12 | ```bash 13 | cd python 14 | pip install -r requirements.txt 15 | ``` 16 | 17 | Or install globally: 18 | ```bash 19 | pip install pycryptodome 20 | ``` 21 | 22 | ## Usage 23 | 24 | ### Command Line 25 | 26 | ```bash 27 | python decrypt.py 28 | # or 29 | python3 decrypt.py 30 | ``` 31 | 32 | ### As Module 33 | 34 | ```python 35 | from decrypt import decrypt 36 | 37 | uuid = 'your-uuid' 38 | encrypted = 'base64-encrypted-data' 39 | password = 'your-password' 40 | 41 | try: 42 | data = decrypt(uuid, encrypted, password) 43 | print('Decrypted data:', data) 44 | except Exception as error: 45 | print(f'Decryption failed: {error}') 46 | ``` 47 | 48 | ### Flask API Example 49 | 50 | ```python 51 | from flask import Flask, request, jsonify 52 | from decrypt import decrypt 53 | 54 | app = Flask(__name__) 55 | 56 | @app.route('/decrypt', methods=['POST']) 57 | def decrypt_endpoint(): 58 | data = request.json 59 | 60 | try: 61 | result = decrypt(data['uuid'], data['encrypted'], data['password']) 62 | return jsonify({'success': True, 'data': result}) 63 | except Exception as e: 64 | return jsonify({'success': False, 'error': str(e)}), 400 65 | 66 | if __name__ == '__main__': 67 | app.run(debug=True) 68 | ``` 69 | 70 | ### Django Integration 71 | 72 | ```python 73 | # views.py 74 | from django.http import JsonResponse 75 | from django.views.decorators.csrf import csrf_exempt 76 | import json 77 | from .decrypt import decrypt 78 | 79 | @csrf_exempt 80 | def decrypt_view(request): 81 | if request.method == 'POST': 82 | data = json.loads(request.body) 83 | 84 | try: 85 | result = decrypt(data['uuid'], data['encrypted'], data['password']) 86 | return JsonResponse({'success': True, 'data': result}) 87 | except Exception as e: 88 | return JsonResponse({'success': False, 'error': str(e)}, status=400) 89 | ``` 90 | 91 | ## Dependencies 92 | 93 | - **PyCryptodome**: Modern cryptographic library for Python 94 | - Provides AES encryption/decryption 95 | - PKCS7 padding utilities 96 | - Cross-platform compatibility 97 | 98 | ## Algorithm Details 99 | 100 | - **Algorithm**: AES-128-CBC 101 | - **Key**: MD5(uuid + "-" + password).substring(0, 16) 102 | - **IV**: Fixed 16 bytes of zeros 103 | - **Padding**: PKCS7 104 | - **Encoding**: Base64 105 | 106 | ## Performance 107 | 108 | - Decryption time: ~3-5ms for typical CookieCloud data 109 | - Memory usage: ~10-15MB 110 | - Pure Python implementation with C extensions for crypto operations 111 | 112 | ## Troubleshooting 113 | 114 | ### Installation Issues 115 | 116 | If you encounter installation issues with PyCryptodome: 117 | 118 | **Windows:** 119 | ```bash 120 | pip install pycryptodome 121 | ``` 122 | 123 | **macOS:** 124 | ```bash 125 | pip install pycryptodome 126 | # or if using Homebrew Python 127 | pip3 install pycryptodome 128 | ``` 129 | 130 | **Linux:** 131 | ```bash 132 | pip install pycryptodome 133 | # or 134 | sudo apt-get install python3-pycryptodome # Debian/Ubuntu 135 | ``` 136 | 137 | ### Alternative: PyCrypto 138 | 139 | If PyCryptodome is not available, you can use the older PyCrypto library: 140 | 141 | ```bash 142 | pip install pycrypto 143 | ``` 144 | 145 | The code automatically detects and uses either library. 146 | -------------------------------------------------------------------------------- /examples/fixediv/python/decrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | CookieCloud Fixed IV Decryption - Python Implementation 5 | 6 | Algorithm: AES-128-CBC 7 | Key: MD5(uuid + "-" + password).substring(0, 16) 8 | IV: Fixed 16 bytes of zeros (0x00000000000000000000000000000000) 9 | Padding: PKCS7 10 | Encoding: Base64 11 | """ 12 | 13 | import hashlib 14 | import json 15 | import base64 16 | import os 17 | from pathlib import Path 18 | 19 | try: 20 | from Crypto.Cipher import AES 21 | from Crypto.Util.Padding import unpad 22 | except ImportError: 23 | try: 24 | from Cryptodome.Cipher import AES 25 | from Cryptodome.Util.Padding import unpad 26 | except ImportError: 27 | print("❌ Error: PyCrypto or PyCryptodome is required") 28 | print("Install with: pip install -r requirements.txt") 29 | exit(1) 30 | 31 | 32 | def decrypt(uuid: str, encrypted: str, password: str) -> dict: 33 | """ 34 | Decrypt CookieCloud data using standard Python crypto 35 | 36 | Args: 37 | uuid: User UUID 38 | encrypted: Base64 encrypted data 39 | password: Password 40 | 41 | Returns: 42 | Decrypted data dictionary 43 | """ 44 | # 1. Generate key: first 16 characters of MD5(uuid + "-" + password) 45 | key_input = f"{uuid}-{password}" 46 | hash_result = hashlib.md5(key_input.encode('utf-8')).hexdigest() 47 | key = hash_result[:16].encode('utf-8') 48 | 49 | # 2. Fixed IV: 16 bytes of zeros 50 | iv = b'\x00' * 16 51 | 52 | # 3. Decode base64 encrypted data 53 | encrypted_data = base64.b64decode(encrypted) 54 | 55 | # 4. Create AES-128-CBC cipher 56 | cipher = AES.new(key, AES.MODE_CBC, iv) 57 | 58 | # 5. Decrypt and remove padding 59 | decrypted = cipher.decrypt(encrypted_data) 60 | unpadded = unpad(decrypted, AES.block_size) 61 | 62 | # 6. Parse JSON 63 | result = json.loads(unpadded.decode('utf-8')) 64 | 65 | return result 66 | 67 | 68 | def print_cookies(cookie_data: dict): 69 | """Pretty print cookie data""" 70 | print('\n🍪 Cookie Data:') 71 | for domain, cookies in cookie_data.items(): 72 | print(f'\n📍 {domain}:') 73 | for i, cookie in enumerate(cookies, 1): 74 | print(f' {i}. {cookie["name"]} = {cookie["value"]}') 75 | if cookie.get('path'): 76 | print(f' Path: {cookie["path"]}') 77 | if cookie.get('secure'): 78 | print(f' Secure: {cookie["secure"]}') 79 | if cookie.get('httpOnly'): 80 | print(f' HttpOnly: {cookie["httpOnly"]}') 81 | 82 | 83 | def print_local_storage(local_storage_data: dict): 84 | """Pretty print local storage data""" 85 | print('\n💾 Local Storage Data:') 86 | for domain, storage in local_storage_data.items(): 87 | print(f'\n📍 {domain}:') 88 | for key, value in storage.items(): 89 | display_value = value[:50] + '...' if len(value) > 50 else value 90 | print(f' {key} = {display_value}') 91 | 92 | 93 | def main(): 94 | print('=== CookieCloud Fixed IV Decryption - Python ===\n') 95 | 96 | # Test parameters 97 | uuid = 'jNp1T2qZ6shwVW9VmjLvp1' 98 | password = 'iZ4PCqzfJcHyiwAQcCuupD' 99 | data_file = Path(__file__).parent.parent / 'jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json' 100 | 101 | print('📋 Test Parameters:') 102 | print(f'UUID: {uuid}') 103 | print(f'Password: {password}') 104 | print(f'Data File: {data_file.name}') 105 | 106 | try: 107 | # Read encrypted data 108 | with open(data_file, 'r', encoding='utf-8') as f: 109 | raw_data = f.read() 110 | data = json.loads(raw_data) 111 | 112 | print(f'\n🔐 Encrypted Data Length: {len(data["encrypted"])} characters') 113 | print(f'Encrypted Data (first 50 chars): {data["encrypted"][:50]}...') 114 | 115 | # Decrypt 116 | print('\n🔓 Decrypting...') 117 | decrypted = decrypt(uuid, data['encrypted'], password) 118 | 119 | print('✅ Decryption successful!') 120 | print(f'\n📊 Decrypted Data Summary:') 121 | print(f'- Cookie domains: {len(decrypted.get("cookie_data", {}))}') 122 | print(f'- Local storage domains: {len(decrypted.get("local_storage_data", {}))}') 123 | print(f'- Update time: {decrypted.get("update_time")}') 124 | 125 | # Print detailed data 126 | if decrypted.get('cookie_data'): 127 | print_cookies(decrypted['cookie_data']) 128 | 129 | if decrypted.get('local_storage_data'): 130 | print_local_storage(decrypted['local_storage_data']) 131 | 132 | print('\n🎉 Python decryption completed successfully!') 133 | 134 | except FileNotFoundError: 135 | print(f'❌ Error: Data file not found: {data_file}') 136 | exit(1) 137 | except Exception as error: 138 | print(f'❌ Decryption failed: {error}') 139 | exit(1) 140 | 141 | 142 | if __name__ == '__main__': 143 | main() 144 | -------------------------------------------------------------------------------- /examples/fixediv/python/requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome>=3.15.0 2 | -------------------------------------------------------------------------------- /examples/fixediv/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # CookieCloud Fixed IV - Cross-Language Test Script 4 | # Tests all language implementations with the same test data 5 | 6 | echo "🚀 Testing CookieCloud Fixed IV Decryption Across Languages" 7 | echo "============================================================" 8 | 9 | # Test parameters 10 | UUID="jNp1T2qZ6shwVW9VmjLvp1" 11 | PASSWORD="iZ4PCqzfJcHyiwAQcCuupD" 12 | DATA_FILE="jNp1T2qZ6shwVW9VmjLvp1_iZ4PCqzfJcHyiwAQcCuupD.json" 13 | 14 | echo "📋 Test Parameters:" 15 | echo " UUID: $UUID" 16 | echo " Password: $PASSWORD" 17 | echo " Data File: $DATA_FILE" 18 | echo "" 19 | 20 | # Check if data file exists 21 | if [ ! -f "$DATA_FILE" ]; then 22 | echo "❌ Error: Test data file '$DATA_FILE' not found!" 23 | echo " Please ensure the data file is in the current directory." 24 | exit 1 25 | fi 26 | 27 | PASSED=0 28 | FAILED=0 29 | SKIPPED=0 30 | 31 | # Function to run test and check result 32 | run_test() { 33 | local name="$1" 34 | local dir="$2" 35 | local command="$3" 36 | local check_command="$4" 37 | 38 | echo "🧪 Testing $name..." 39 | echo " Directory: $dir" 40 | echo " Command: $command" 41 | 42 | # Check if directory exists 43 | if [ ! -d "$dir" ]; then 44 | echo " ⚠️ Skipped: Directory not found" 45 | ((SKIPPED++)) 46 | echo "" 47 | return 48 | fi 49 | 50 | # Check if required tools are available 51 | if [ ! -z "$check_command" ]; then 52 | if ! $check_command >/dev/null 2>&1; then 53 | echo " ⚠️ Skipped: Required tools not available" 54 | ((SKIPPED++)) 55 | echo "" 56 | return 57 | fi 58 | fi 59 | 60 | # Change to test directory and run 61 | cd "$dir" || { 62 | echo " ❌ FAILED: Cannot change to directory" 63 | ((FAILED++)) 64 | echo "" 65 | return 66 | } 67 | 68 | # Run the test with timeout (compatible with both Linux and macOS) 69 | if command -v timeout >/dev/null 2>&1; then 70 | # Linux timeout 71 | if timeout 60s bash -c "$command" >/dev/null 2>&1; then 72 | echo " ✅ PASSED" 73 | ((PASSED++)) 74 | else 75 | echo " ❌ FAILED" 76 | ((FAILED++)) 77 | echo " 💡 Try running manually: cd $dir && $command" 78 | fi 79 | elif command -v gtimeout >/dev/null 2>&1; then 80 | # macOS gtimeout (brew install coreutils) 81 | if gtimeout 60s bash -c "$command" >/dev/null 2>&1; then 82 | echo " ✅ PASSED" 83 | ((PASSED++)) 84 | else 85 | echo " ❌ FAILED" 86 | ((FAILED++)) 87 | echo " 💡 Try running manually: cd $dir && $command" 88 | fi 89 | else 90 | # No timeout available, run directly 91 | if bash -c "$command" >/dev/null 2>&1; then 92 | echo " ✅ PASSED" 93 | ((PASSED++)) 94 | else 95 | echo " ❌ FAILED" 96 | ((FAILED++)) 97 | echo " 💡 Try running manually: cd $dir && $command" 98 | fi 99 | fi 100 | 101 | # Return to parent directory 102 | cd .. 103 | echo "" 104 | } 105 | 106 | # Test 1: Node.js 107 | run_test "Node.js" "nodejs" "node decrypt.js" "command -v node" 108 | 109 | # Test 2: Python 110 | run_test "Python" "python" "pip install -r requirements.txt >/dev/null 2>&1 && python decrypt.py" "command -v python" 111 | 112 | # Test 3: Go 113 | run_test "Go" "go" "go mod tidy >/dev/null 2>&1 && go run decrypt.go" "command -v go" 114 | 115 | # Test 4: PHP 116 | run_test "PHP" "php" "php decrypt.php" "command -v php" 117 | 118 | # Test 5: Java (Maven version) 119 | echo "🧪 Testing Java (Maven)..." 120 | echo " Directory: java" 121 | echo " Command: mvn -q exec:java -Dexec.mainClass=\"com.cookiecloud.decrypt.DecryptMain\"" 122 | 123 | if [ -d "java" ]; then 124 | if command -v mvn >/dev/null 2>&1; then 125 | cd java || { 126 | echo " ❌ FAILED: Cannot change to directory" 127 | ((FAILED++)) 128 | echo "" 129 | } 130 | 131 | # Try to run with Maven (with timeout compatibility) 132 | maven_success=false 133 | if command -v timeout >/dev/null 2>&1; then 134 | timeout 120s mvn -q exec:java -Dexec.mainClass="com.cookiecloud.decrypt.DecryptMain" >/dev/null 2>&1 && maven_success=true 135 | elif command -v gtimeout >/dev/null 2>&1; then 136 | gtimeout 120s mvn -q exec:java -Dexec.mainClass="com.cookiecloud.decrypt.DecryptMain" >/dev/null 2>&1 && maven_success=true 137 | else 138 | mvn -q exec:java -Dexec.mainClass="com.cookiecloud.decrypt.DecryptMain" >/dev/null 2>&1 && maven_success=true 139 | fi 140 | 141 | if [ "$maven_success" = true ]; then 142 | echo " ✅ PASSED" 143 | ((PASSED++)) 144 | else 145 | echo " ❌ FAILED (May need to run 'mvn install' first)" 146 | ((FAILED++)) 147 | echo " 💡 Try: cd java && mvn install && mvn exec:java -Dexec.mainClass=\"com.cookiecloud.decrypt.DecryptMain\"" 148 | fi 149 | cd .. 150 | else 151 | echo " ⚠️ Skipped: Maven not available" 152 | ((SKIPPED++)) 153 | fi 154 | else 155 | echo " ⚠️ Skipped: Directory not found" 156 | ((SKIPPED++)) 157 | fi 158 | echo "" 159 | 160 | # Test 6: Java Simple (no dependencies) 161 | echo "🧪 Testing Java Simple..." 162 | echo " Directory: java-simple" 163 | echo " Command: javac DecryptSimple.java && java DecryptSimple" 164 | 165 | if [ -d "java-simple" ]; then 166 | if command -v javac >/dev/null 2>&1 && command -v java >/dev/null 2>&1; then 167 | cd java-simple || { 168 | echo " ❌ FAILED: Cannot change to directory" 169 | ((FAILED++)) 170 | echo "" 171 | } 172 | 173 | # Try to compile and run 174 | if javac DecryptSimple.java 2>/dev/null; then 175 | java_success=false 176 | if command -v timeout >/dev/null 2>&1; then 177 | timeout 60s java DecryptSimple >/dev/null 2>&1 && java_success=true 178 | elif command -v gtimeout >/dev/null 2>&1; then 179 | gtimeout 60s java DecryptSimple >/dev/null 2>&1 && java_success=true 180 | else 181 | java DecryptSimple >/dev/null 2>&1 && java_success=true 182 | fi 183 | 184 | if [ "$java_success" = true ]; then 185 | echo " ✅ PASSED" 186 | ((PASSED++)) 187 | # Clean up 188 | rm -f *.class 189 | else 190 | echo " ❌ FAILED (Runtime error)" 191 | ((FAILED++)) 192 | fi 193 | else 194 | echo " ❌ FAILED (Compilation error)" 195 | ((FAILED++)) 196 | fi 197 | cd .. 198 | else 199 | echo " ⚠️ Skipped: Java not available" 200 | ((SKIPPED++)) 201 | fi 202 | else 203 | echo " ⚠️ Skipped: Directory not found" 204 | ((SKIPPED++)) 205 | fi 206 | echo "" 207 | 208 | # Summary 209 | echo "📊 Test Summary" 210 | echo "===============" 211 | echo "✅ Passed: $PASSED" 212 | echo "❌ Failed: $FAILED" 213 | echo "⚠️ Skipped: $SKIPPED" 214 | echo "" 215 | 216 | if [ $FAILED -eq 0 ]; then 217 | if [ $PASSED -gt 0 ]; then 218 | echo "🎉 All available language implementations passed!" 219 | echo "" 220 | echo "🔐 Algorithm Verification:" 221 | echo " All implementations successfully decrypted the same data using:" 222 | echo " - Algorithm: AES-128-CBC" 223 | echo " - Key: MD5(uuid + '-' + password).substring(0, 16)" 224 | echo " - IV: Fixed 16 bytes of zeros" 225 | echo " - Padding: PKCS7" 226 | echo " - Encoding: Base64" 227 | echo "" 228 | echo "✨ This proves perfect cross-language compatibility!" 229 | else 230 | echo "⚠️ No tests were run. Please check if the required tools are installed." 231 | fi 232 | else 233 | echo "⚠️ Some tests failed. Common reasons:" 234 | echo " - Missing dependencies (PyCryptodome for Python, Jackson for Java)" 235 | echo " - Environment issues" 236 | echo " - File permissions" 237 | echo " - Network timeouts during dependency installation" 238 | echo "" 239 | echo "💡 Try running individual tests manually:" 240 | echo " - Node.js: cd nodejs && node decrypt.js" 241 | echo " - Python: cd python && pip install -r requirements.txt && python decrypt.py" 242 | echo " - Go: cd go && go run decrypt.go" 243 | echo " - PHP: cd php && php decrypt.php" 244 | echo " - Java (Maven): cd java && mvn install && mvn exec:java -Dexec.mainClass=\"com.cookiecloud.decrypt.DecryptMain\"" 245 | echo " - Java Simple: cd java-simple && javac DecryptSimple.java && java DecryptSimple" 246 | fi 247 | 248 | echo "" 249 | echo "🔗 For detailed documentation, see individual README.md files in each language directory" 250 | echo "🔗 For algorithm details, see the main README.md file" -------------------------------------------------------------------------------- /examples/playwright/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /playwright/.cache/ 5 | 6 | tests/config.js -------------------------------------------------------------------------------- /examples/playwright/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@playwright/test": "^1.29.2" 8 | }, 9 | "scripts": {}, 10 | "dependencies": { 11 | "cross-fetch": "^3.1.5", 12 | "crypto-js": "^4.1.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/playwright/playwright.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { devices } = require('@playwright/test'); 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | 11 | /** 12 | * @see https://playwright.dev/docs/test-configuration 13 | * @type {import('@playwright/test').PlaywrightTestConfig} 14 | */ 15 | const config = { 16 | testDir: './tests', 17 | /* Maximum time one test can run for. */ 18 | timeout: 30 * 1000, 19 | expect: { 20 | /** 21 | * Maximum time expect() should wait for the condition to be met. 22 | * For example in `await expect(locator).toHaveText();` 23 | */ 24 | timeout: 5000 25 | }, 26 | /* Run tests in files in parallel */ 27 | fullyParallel: true, 28 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 29 | forbidOnly: !!process.env.CI, 30 | /* Retry on CI only */ 31 | retries: process.env.CI ? 2 : 0, 32 | /* Opt out of parallel tests on CI. */ 33 | workers: process.env.CI ? 1 : undefined, 34 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 35 | reporter: 'html', 36 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 37 | use: { 38 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 39 | actionTimeout: 0, 40 | /* Base URL to use in actions like `await page.goto('/')`. */ 41 | // baseURL: 'http://localhost:3000', 42 | 43 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 44 | trace: 'on-first-retry', 45 | }, 46 | 47 | /* Configure projects for major browsers */ 48 | projects: [ 49 | { 50 | name: 'chromium', 51 | use: { 52 | ...devices['Desktop Chrome'], 53 | }, 54 | }, 55 | 56 | { 57 | name: 'firefox', 58 | use: { 59 | ...devices['Desktop Firefox'], 60 | }, 61 | }, 62 | 63 | { 64 | name: 'webkit', 65 | use: { 66 | ...devices['Desktop Safari'], 67 | }, 68 | }, 69 | 70 | /* Test against mobile viewports. */ 71 | // { 72 | // name: 'Mobile Chrome', 73 | // use: { 74 | // ...devices['Pixel 5'], 75 | // }, 76 | // }, 77 | // { 78 | // name: 'Mobile Safari', 79 | // use: { 80 | // ...devices['iPhone 12'], 81 | // }, 82 | // }, 83 | 84 | /* Test against branded browsers. */ 85 | // { 86 | // name: 'Microsoft Edge', 87 | // use: { 88 | // channel: 'msedge', 89 | // }, 90 | // }, 91 | // { 92 | // name: 'Google Chrome', 93 | // use: { 94 | // channel: 'chrome', 95 | // }, 96 | // }, 97 | ], 98 | 99 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 100 | // outputDir: 'test-results/', 101 | 102 | /* Run your local dev server before starting the tests */ 103 | // webServer: { 104 | // command: 'npm run start', 105 | // port: 3000, 106 | // }, 107 | }; 108 | 109 | module.exports = config; 110 | -------------------------------------------------------------------------------- /examples/playwright/tests/config.example.js: -------------------------------------------------------------------------------- 1 | export const COOKIE_CLOUD_HOST = 'http://127.0.0.1:8088'; 2 | export const COOKIE_CLOUD_UUID = ''; 3 | export const COOKIE_CLOUD_PASSWORD = ''; -------------------------------------------------------------------------------- /examples/playwright/tests/example.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require('@playwright/test'); 2 | const { COOKIE_CLOUD_HOST, COOKIE_CLOUD_UUID, COOKIE_CLOUD_PASSWORD } = require('./config') ; 3 | 4 | test('使用CookieCloud访问nexusphp', async ({ page, browser }) => { 5 | // 读取云端cookie并解密 6 | const cookies = await cloud_cookie(COOKIE_CLOUD_HOST, COOKIE_CLOUD_UUID, COOKIE_CLOUD_PASSWORD); 7 | // 添加cookie到浏览器上下文 8 | const context = await browser.newContext(); 9 | await context.addCookies(cookies); 10 | page = await context.newPage(); 11 | // 这之后已经带着Cookie了,按正常流程访问 12 | await page.goto('https://demo.nexusphp.org/index.php'); 13 | await expect(page.getByRole('link', { name: 'magik' })).toHaveText("magik"); 14 | await context.close(); 15 | }); 16 | 17 | 18 | async function cloud_cookie( host, uuid, password, crypto_type = 'legacy' ) 19 | { 20 | const fetch = require('cross-fetch'); 21 | let url = host+'/get/'+uuid; 22 | // 如果指定了加密算法,添加查询参数 23 | if (crypto_type && crypto_type !== 'legacy') { 24 | url += `?crypto_type=${crypto_type}`; 25 | } 26 | const ret = await fetch(url); 27 | const json = await ret.json(); 28 | let cookies = []; 29 | if( json && json.encrypted ) 30 | { 31 | // 优先使用参数指定的算法,其次使用服务器返回的算法,最后使用legacy 32 | const useCryptoType = crypto_type || json.crypto_type || 'legacy'; 33 | const {cookie_data, local_storage_data} = cookie_decrypt(uuid, json.encrypted, password, useCryptoType); 34 | for( const key in cookie_data ) 35 | { 36 | // merge cookie_data[key] to cookies 37 | cookies = cookies.concat(cookie_data[key].map( item => { 38 | if( item.sameSite == 'unspecified' ) item.sameSite = 'Lax'; 39 | return item; 40 | } )); 41 | } 42 | } 43 | return cookies; 44 | } 45 | 46 | function cookie_decrypt( uuid, encrypted, password, crypto_type = 'legacy' ) 47 | { 48 | const CryptoJS = require('crypto-js'); 49 | const hash = CryptoJS.MD5(uuid+'-'+password).toString(); 50 | const the_key = hash.substring(0, 16); 51 | 52 | if (crypto_type === 'aes-128-cbc-fixed') { 53 | // 新的标准 AES-128-CBC 算法,使用固定 IV 54 | const fixedIv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); // 16字节的0 55 | const options = { 56 | iv: fixedIv, 57 | mode: CryptoJS.mode.CBC, 58 | padding: CryptoJS.pad.Pkcs7 59 | }; 60 | // 直接解密原始加密数据 61 | const decrypted = CryptoJS.AES.decrypt(encrypted, CryptoJS.enc.Utf8.parse(the_key), options).toString(CryptoJS.enc.Utf8); 62 | const parsed = JSON.parse(decrypted); 63 | return parsed; 64 | } else { 65 | // 原有的 legacy 算法 66 | const decrypted = CryptoJS.AES.decrypt(encrypted, the_key).toString(CryptoJS.enc.Utf8); 67 | const parsed = JSON.parse(decrypted); 68 | return parsed; 69 | } 70 | } -------------------------------------------------------------------------------- /examples/playwright/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@playwright/test@^1.29.2": 6 | version "1.29.2" 7 | resolved "https://registry.npmmirror.com/@playwright/test/-/test-1.29.2.tgz#c48184721d0f0b7627a886e2ec42f1efb2be339d" 8 | integrity sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg== 9 | dependencies: 10 | "@types/node" "*" 11 | playwright-core "1.29.2" 12 | 13 | "@types/node@*": 14 | version "18.11.18" 15 | resolved "https://registry.npmmirror.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" 16 | integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== 17 | 18 | cross-fetch@^3.1.5: 19 | version "3.1.5" 20 | resolved "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" 21 | integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== 22 | dependencies: 23 | node-fetch "2.6.7" 24 | 25 | crypto-js@^4.1.1: 26 | version "4.1.1" 27 | resolved "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" 28 | integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== 29 | 30 | node-fetch@2.6.7: 31 | version "2.6.7" 32 | resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 33 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 34 | dependencies: 35 | whatwg-url "^5.0.0" 36 | 37 | playwright-core@1.29.2: 38 | version "1.29.2" 39 | resolved "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.29.2.tgz#2e8347e7e8522409f22b244e600e703b64022406" 40 | integrity sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA== 41 | 42 | tr46@~0.0.3: 43 | version "0.0.3" 44 | resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 45 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 46 | 47 | webidl-conversions@^3.0.0: 48 | version "3.0.1" 49 | resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 50 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 51 | 52 | whatwg-url@^5.0.0: 53 | version "5.0.0" 54 | resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 55 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 56 | dependencies: 57 | tr46 "~0.0.3" 58 | webidl-conversions "^3.0.0" 59 | -------------------------------------------------------------------------------- /ext/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /ext/assets/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ext/components/counter.ts: -------------------------------------------------------------------------------- 1 | export function setupCounter(element: HTMLButtonElement) { 2 | let counter = 0; 3 | const setCounter = (count: number) => { 4 | counter = count; 5 | element.innerHTML = `count is ${counter}`; 6 | }; 7 | element.addEventListener('click', () => setCounter(counter + 1)); 8 | setCounter(0); 9 | } 10 | -------------------------------------------------------------------------------- /ext/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | import { upload_cookie, download_cookie, load_data, save_data, sleep } from '../utils/functions'; 2 | import browser from 'webextension-polyfill'; 3 | 4 | export default defineBackground(() => { 5 | console.log('CookieCloud Background Script Started', { id: browser.runtime.id }); 6 | 7 | browser.runtime.onInstalled.addListener(function (details) { 8 | if (details.reason == "install") { 9 | browser.alarms.create('bg_1_minute', { 10 | when: Date.now(), 11 | periodInMinutes: 1 12 | }); 13 | } 14 | else if (details.reason == "update") { 15 | browser.alarms.create('bg_1_minute', { 16 | when: Date.now(), 17 | periodInMinutes: 1 18 | }); 19 | } 20 | }); 21 | 22 | browser.alarms.onAlarm.addListener(async a => { 23 | if (a.name == 'bg_1_minute') { 24 | // console.log( 'bg_1_minute' ); 25 | const config = await load_data("COOKIE_SYNC_SETTING"); 26 | if (config) { 27 | if (config.type && config.type == 'pause') { 28 | console.log("Pause mode, no sync"); 29 | return true; 30 | } 31 | 32 | // Get current minute count 33 | const now = new Date(); 34 | const minute = now.getMinutes(); 35 | const hour = now.getHours(); 36 | const day = now.getDate(); 37 | const minute_count = (day * 24 + hour) * 60 + minute; 38 | 39 | if (config.uuid) { 40 | // If current minute count is divisible by interval, execute sync 41 | if (parseInt(config.interval) < 1 || minute_count % config.interval == 0) { 42 | // Start sync 43 | console.log(`Execute sync ${minute_count} ${config.interval}`); 44 | if (config.type && config.type == 'down') { 45 | // Download cookies from server and write to local 46 | const result = await download_cookie(config); 47 | if (result && result['action'] == 'done') 48 | console.log("Download success"); 49 | else 50 | console.log(result); 51 | } else { 52 | const result = await upload_cookie(config); 53 | if (result && result['action'] == 'done') 54 | console.log("Upload success"); 55 | else 56 | console.log(result); 57 | } 58 | } else { 59 | // console.log(`Not sync time yet ${minute_count} ${config.interval}`); 60 | } 61 | } 62 | 63 | if (config.keep_live) { 64 | // Split by lines, each line format: url|interval 65 | const keep_live = config.keep_live?.trim()?.split("\n"); 66 | for (let i = 0; i < keep_live.length; i++) { 67 | const line = keep_live[i]; 68 | // 如果 line 以 #开头,则跳过 69 | if (line.trim().startsWith("#")) continue; 70 | const parts = line.split("|"); 71 | const url = parts[0]; 72 | const interval = parts[1] ? parseInt(parts[1]) : 60; 73 | if (interval > 0 && minute_count % interval == 0) { 74 | // Start visit 75 | console.log(`keep live ${url} ${minute_count} ${interval}`); 76 | 77 | // Check if target page is already open, if so, don't open again 78 | // Besides being unnecessary, it also avoids duplicate opening due to network delays 79 | const [exists_tab] = await browser.tabs.query({"url": `${url.trim().replace(/\/+$/, '')}/*`}); 80 | if (exists_tab && exists_tab.id) { 81 | console.log(`tab exists ${exists_tab.id}`, exists_tab); 82 | if (!exists_tab.active) { 83 | // refresh tab 84 | console.log(`Background status, refresh page`); 85 | await browser.tabs.reload(exists_tab.id); 86 | } else { 87 | console.log(`Foreground status, skip`); 88 | } 89 | return true; 90 | } else { 91 | console.log(`tab not exists, open in background`); 92 | } 93 | 94 | // chrome tab create 95 | const tab = await browser.tabs.create({"url": url, "active": false, "pinned": true}); 96 | // Wait 5 seconds then close 97 | await sleep(5000); 98 | if (tab.id) { 99 | await browser.tabs.remove(tab.id); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /ext/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | import { load_data, save_data, remove_data } from '../utils/functions'; 2 | 3 | export default defineContentScript({ 4 | matches: [''], 5 | main() { 6 | console.log('CookieCloud Content Script Loaded'); 7 | 8 | window.addEventListener("load", async () => { 9 | // Get current domain 10 | const host = window.location.hostname; 11 | const config = await load_data("COOKIE_SYNC_SETTING"); 12 | if (config?.domains) { 13 | const domains = config.domains?.trim().split("\n"); 14 | // Check if domain partially matches each domain in domains 15 | let matched = false; 16 | for (const domain of domains) { 17 | if (host.includes(domain)) matched = true; 18 | } 19 | if (domains.length > 0 && !matched) return false; 20 | } 21 | 22 | if (config?.type && config.type == 'down') { 23 | const the_data = await load_data("LS-" + host); 24 | // console.log( "the_data", the_data ); 25 | if (the_data) { 26 | // Override local localStorage 27 | for (const key in the_data) { 28 | localStorage.setItem(key, the_data[key]); 29 | } 30 | // Clear browser storage to avoid multiple overwrites 31 | await remove_data("LS-" + host); 32 | } 33 | } else { 34 | // Get all localStorage of current page 35 | const all = localStorage; 36 | const keys = Object.keys(all); 37 | const values = Object.values(all); 38 | const result: any = {}; 39 | for (let i = 0; i < keys.length; i++) { 40 | result[keys[i]] = values[i]; 41 | } 42 | // Store it in browser storage 43 | if (Object.keys(result).length > 0) { 44 | await save_data("LS-" + host, result); 45 | } 46 | } 47 | }); 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /ext/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CookieCloud 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ext/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import CookieCloudPopup from './App'; 4 | import './style.css'; 5 | 6 | const container = document.getElementById('app'); 7 | if (container) { 8 | const root = createRoot(container); 9 | root.render(); 10 | } -------------------------------------------------------------------------------- /ext/entrypoints/popup/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer components { 6 | .btn { 7 | @apply px-4 py-2 rounded font-medium cursor-pointer transition-all duration-300; 8 | } 9 | 10 | .btn-primary { 11 | @apply bg-blue-500 text-white hover:bg-blue-600; 12 | } 13 | 14 | .btn-success { 15 | @apply bg-green-500 text-white hover:bg-green-600; 16 | } 17 | 18 | .form-input { 19 | @apply w-full px-3 py-2 border-2 border-gray-300 rounded text-sm transition-colors duration-300 focus:outline-none focus:border-blue-500; 20 | } 21 | 22 | .form-textarea { 23 | @apply w-full px-3 py-2 border-2 border-gray-300 rounded text-sm transition-colors duration-300 focus:outline-none focus:border-blue-500 resize-y min-h-[60px]; 24 | font-family: inherit; 25 | } 26 | 27 | .form-select { 28 | @apply w-full px-3 py-2 border-2 border-gray-300 rounded text-sm transition-colors duration-300 focus:outline-none focus:border-blue-500; 29 | } 30 | } 31 | 32 | .btn-primary:hover { 33 | background-color: #2980b9; 34 | } 35 | 36 | .btn-success { 37 | background-color: #27ae60; 38 | color: white; 39 | } 40 | 41 | .btn-success:hover { 42 | background-color: #229954; 43 | } 44 | 45 | .overwrite-warning { 46 | margin-top: 15px; 47 | padding: 12px; 48 | background-color: #fff3cd; 49 | border: 1px solid #ffeaa7; 50 | border-radius: 4px; 51 | color: #856404; 52 | font-size: 12px; 53 | line-height: 1.4; 54 | } 55 | 56 | .overwrite-warning p { 57 | margin: 0; 58 | } 59 | 60 | /* Scrollbar styling */ 61 | ::-webkit-scrollbar { 62 | width: 6px; 63 | } 64 | 65 | ::-webkit-scrollbar-track { 66 | background: #f1f1f1; 67 | } 68 | 69 | ::-webkit-scrollbar-thumb { 70 | background: #c1c1c1; 71 | border-radius: 3px; 72 | } 73 | 74 | ::-webkit-scrollbar-thumb:hover { 75 | background: #a8a8a8; 76 | } 77 | 78 | /* Dark mode support */ 79 | @media (prefers-color-scheme: dark) { 80 | body { 81 | background-color: #2c3e50; 82 | color: #ecf0f1; 83 | } 84 | 85 | #app { 86 | background: #34495e; 87 | } 88 | 89 | .header { 90 | border-bottom-color: #4a5f7a; 91 | } 92 | 93 | .header h2 { 94 | color: #ecf0f1; 95 | } 96 | 97 | .form-group label { 98 | color: #bdc3c7; 99 | } 100 | 101 | .form-group input, 102 | .form-group select, 103 | .form-group textarea { 104 | background-color: #2c3e50; 105 | border-color: #4a5f7a; 106 | color: #ecf0f1; 107 | } 108 | 109 | .form-group input:focus, 110 | .form-group select:focus, 111 | .form-group textarea:focus { 112 | border-color: #3498db; 113 | } 114 | 115 | .overwrite-warning { 116 | background-color: #f39c12; 117 | border-color: #e67e22; 118 | color: #2c3e50; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ext/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookie-cloud", 3 | "displayName": "CookieCloud", 4 | "description": "Sync cookies between browses", 5 | "private": true, 6 | "version": "0.3.1", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "wxt", 10 | "dev:firefox": "wxt -b firefox", 11 | "build": "wxt build", 12 | "build:firefox": "wxt build -b firefox", 13 | "zip": "wxt zip", 14 | "zip:firefox": "wxt zip -b firefox", 15 | "compile": "tsc --noEmit", 16 | "postinstall": "wxt prepare" 17 | }, 18 | "devDependencies": { 19 | "@types/chrome": "^0.1.3", 20 | "@types/crypto-js": "^4.2.2", 21 | "@types/pako": "^2.0.3", 22 | "@types/react": "^18.2.0", 23 | "@types/react-copy-to-clipboard": "^5.0.7", 24 | "@types/react-dom": "^18.2.0", 25 | "@types/webextension-polyfill": "^0.12.3", 26 | "autoprefixer": "^10.4.17", 27 | "postcss": "^8.4.35", 28 | "tailwindcss": "^3.4.1", 29 | "typescript": "^5.8.3", 30 | "wxt": "^0.20.6" 31 | }, 32 | "dependencies": { 33 | "@wxt-dev/webextension-polyfill": "^1.0.0", 34 | "crypto-js": "^4.2.0", 35 | "js-base64": "^3.7.8", 36 | "pako": "^2.1.0", 37 | "react": "^18.2.0", 38 | "react-copy-to-clipboard": "^5.1.0", 39 | "react-dom": "^18.2.0", 40 | "short-uuid": "^5.2.0", 41 | "webextension-polyfill": "^0.12.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ext/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /ext/public/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "CookieCloud" 4 | }, 5 | "appDesc": { 6 | "message": "CookieCloud is a tool for syncing browser cookies to a self-hosted server.", 7 | "description": "Uses end-to-end encryption to ensure your cookies are secure." 8 | }, 9 | "test": { 10 | "message": "Test" 11 | }, 12 | "fullMessagePlease": { 13 | "message": "Please fill in all the information" 14 | }, 15 | "actionNotAllowedInPause": { 16 | "message": "This action cannot be performed while paused" 17 | }, 18 | "success": { 19 | "message": "Success" 20 | }, 21 | "failedCheckInfo": { 22 | "message": "Failed, please check if the information entered is correct" 23 | }, 24 | "saveSucess": { 25 | "message": "Save Successful" 26 | }, 27 | "workingMode": { 28 | "message": "Mode" 29 | }, 30 | "upToServer": { 31 | "message": "Upload to Server" 32 | }, 33 | "overwriteToBrowser": { 34 | "message": "Overwrite to Browser" 35 | }, 36 | "pauseSync": { 37 | "message": "Pause Sync" 38 | }, 39 | "overwriteModeDesp": { 40 | "message": "Overwrite mode is mainly used for cloud and read-only browsers. Overwriting Cookies and Local Storage may cause current browser login and modification operations to fail; moreover, some websites do not allow the same cookie to be logged in on multiple browsers at the same time, which may cause account logouts on other browsers." 41 | }, 42 | "serverHost": { 43 | "message": "Server Address" 44 | }, 45 | "serverHostPlaceholder": { 46 | "message": "Please enter server address" 47 | }, 48 | "uuid": { 49 | "message": "User KEY · UUID" 50 | }, 51 | "uuidPlaceholder": { 52 | "message": "Unique User ID" 53 | }, 54 | "reGenerate": { 55 | "message": "Regenerate" 56 | }, 57 | "syncPassword": { 58 | "message": "End-to-End Encryption Password" 59 | }, 60 | "syncPasswordPlaceholder": { 61 | "message": "Data will be unable to be decrypted if lost" 62 | }, 63 | "generate": { 64 | "message": "Generate" 65 | }, 66 | "cookieExpireMinutes": { 67 | "message": "Cookie Expiry Time · Minutes" 68 | }, 69 | "cookieExpireMinutesPlaceholder": { 70 | "message": "0 means it expires immediately after closing the browser" 71 | }, 72 | "syncTimeInterval": { 73 | "message": "Sync Interval · Minutes" 74 | }, 75 | "syncTimeIntervalPlaceholder": { 76 | "message": "At least 10 minutes" 77 | }, 78 | "syncLocalStorageOrNot": { 79 | "message": "Sync Local Storage?" 80 | }, 81 | "yes": { 82 | "message": "Yes" 83 | }, 84 | "no": { 85 | "message": "No" 86 | }, 87 | "requestHeader": { 88 | "message": "Request Header · Optional" 89 | }, 90 | "requestHeaderPlaceholder": { 91 | "message": "Append Header to request for server authentication etc., one per line, format as 'Key:Value', no spaces allowed" 92 | }, 93 | "syncDomainKeyword": { 94 | "message": "Sync Domain Keyword · Optional" 95 | }, 96 | "syncDomainKeywordPlaceholder": { 97 | "message": "One per line, sync all domains containing the keyword, e.g., qq.com, jd.com will include all subdomains, leave blank to sync all by default" 98 | }, 99 | "syncDomainBlacklist": { 100 | "message": "Sync Domain Blacklist · Optional" 101 | }, 102 | "syncDomainBlacklistPlaceholder": { 103 | "message": "The blacklist only takes effect when the sync domain keyword is empty. One domain per line, matching domains will not be synced" 104 | }, 105 | "cookieKeepLive": { 106 | "message": "Cookie Keep Alive · Optional" 107 | }, 108 | "cookieKeepLivePlaceholder": { 109 | "message": "Periodically refresh URL in the background to simulate user activity. One URL per line, default 60 minutes, can specify refresh time with URL|minutes format" 110 | }, 111 | "keepLiveStop": { 112 | "message": "Pause Sync and Keep Alive" 113 | }, 114 | "syncManual": { 115 | "message": "Manual Sync" 116 | }, 117 | "save": { 118 | "message": "Save" 119 | }, 120 | "copySuccess": { 121 | "message": "Copied to clipboard" 122 | }, 123 | "copyFailed": { 124 | "message": "Copy failed, please copy manually" 125 | }, 126 | "cryptoAlgorithm": { 127 | "message": "Encryption Algorithm" 128 | }, 129 | "cryptoLegacy": { 130 | "message": "CryptoJS (Dynamic IV)" 131 | }, 132 | "cryptoAesCbcFixed": { 133 | "message": "AES-128-CBC (Fixed IV)" 134 | }, 135 | "cryptoLegacyDesc": { 136 | "message": "Uses CryptoJS encryption algorithm with dynamic IV generation" 137 | }, 138 | "cryptoAesCbcFixedDesc": { 139 | "message": "Uses standard AES-128-CBC algorithm with fixed IV (0x0)" 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /ext/public/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "CookieCloud" 4 | }, 5 | "appDesc": { 6 | "message": "CookieCloud是一款向自架服务器同步浏览器Cookie的工具。", 7 | "description":"采用端对端加密,保证您的Cookie安全。" 8 | }, 9 | "test": 10 | { 11 | "message":"测试" 12 | }, 13 | "fullMessagePlease": 14 | { 15 | "message":"请填写完整的信息" 16 | }, 17 | "actionNotAllowedInPause": 18 | { 19 | "message":"暂停状态下无法进行此操作" 20 | }, 21 | "success": 22 | { 23 | "message":"成功" 24 | }, 25 | "failedCheckInfo": 26 | { 27 | "message":"失败,请检查填写的信息是否正确" 28 | }, 29 | "saveSucess": 30 | { 31 | "message":"保存成功" 32 | }, 33 | "workingMode": 34 | { 35 | "message":"工作模式" 36 | }, 37 | "upToServer":{ 38 | "message":"上传到服务器" 39 | }, 40 | "overwriteToBrowser":{ 41 | "message":"覆盖到浏览器" 42 | }, 43 | "pauseSync": 44 | { 45 | "message":"暂停同步" 46 | }, 47 | "overwriteModeDesp": 48 | { 49 | "message":"覆盖模式主要用于云端和只读用的浏览器,Cookie和Local Storage覆盖可能导致当前浏览器的登录和修改操作失效;另外部分网站不允许同一个cookie在多个浏览器同时登录,可能导致其他浏览器上账号退出。" 50 | }, 51 | "serverHost": 52 | { 53 | "message":"服务器地址" 54 | }, 55 | "serverHostPlaceholder": 56 | { 57 | "message":"请输入服务器地址" 58 | }, 59 | "uuid": 60 | { 61 | "message":"用户KEY · UUID" 62 | }, 63 | "uuidPlaceholder": 64 | { 65 | "message":"唯一用户ID" 66 | }, 67 | "reGenerate": 68 | { 69 | "message":"重新生成" 70 | }, 71 | "syncPassword": 72 | { 73 | "message":"端对端加密密码" 74 | }, 75 | "syncPasswordPlaceholder": 76 | { 77 | "message":"丢失后数据失效,请妥善保管" 78 | }, 79 | "generate": 80 | { 81 | "message":"自动生成" 82 | }, 83 | "cookieExpireMinutes": 84 | { 85 | "message":"Cookie过期时间·分钟" 86 | }, 87 | "cookieExpireMinutesPlaceholder": 88 | { 89 | "message":"0为关闭浏览器后立刻过期" 90 | }, 91 | "syncTimeInterval": 92 | { 93 | "message":"同步时间间隔·分钟" 94 | }, 95 | "syncTimeIntervalPlaceholder": 96 | { 97 | "message":"最少10分钟" 98 | }, 99 | "syncLocalStorageOrNot": 100 | { 101 | "message":"是否同步Local Storage" 102 | }, 103 | "yes": 104 | { 105 | "message":"是" 106 | }, 107 | "no": 108 | { 109 | "message":"否" 110 | }, 111 | "requestHeader": 112 | { 113 | "message":"请求Header·选填" 114 | }, 115 | "requestHeaderPlaceholder": 116 | { 117 | "message":"在请求时追加Header,用于服务端鉴权等场景,一行一个,格式为'Key:Value',不能有空格" 118 | }, 119 | "syncDomainKeyword": 120 | { 121 | "message":"同步域名关键词·选填" 122 | }, 123 | "syncDomainKeywordPlaceholder": 124 | { 125 | "message":"一行一个,同步包含关键词的全部域名,如qq.com,jd.com会包含全部子域名,留空默认同步全部" 126 | }, 127 | "syncDomainBlacklist": 128 | { 129 | "message":"同步域名黑名单·选填" 130 | }, 131 | "syncDomainBlacklistPlaceholder": 132 | { 133 | "message":"黑名单仅在同步域名关键词为空时生效。一行一个域名,匹配则不参与同步" 134 | }, 135 | "cookieKeepLive": 136 | { 137 | "message":"Cookie保活·选填" 138 | }, 139 | "cookieKeepLivePlaceholder": 140 | { 141 | "message":"定时后台刷新URL,模拟用户活跃。一行一个URL,默认60分钟,可用 URL|分钟数 的方式指定刷新时间" 142 | }, 143 | "keepLiveStop": 144 | { 145 | "message":"暂停同步和保活" 146 | }, 147 | "syncManual": 148 | { 149 | "message":"手动同步" 150 | }, 151 | "save": 152 | { 153 | "message":"保存" 154 | }, 155 | "copySuccess": 156 | { 157 | "message":"已复制到剪贴板" 158 | }, 159 | "copyFailed": 160 | { 161 | "message":"复制失败,请手动复制" 162 | }, 163 | "cryptoAlgorithm": 164 | { 165 | "message":"加密算法" 166 | }, 167 | "cryptoLegacy": 168 | { 169 | "message":"CryptoJS(动态IV)" 170 | }, 171 | "cryptoAesCbcFixed": 172 | { 173 | "message":"AES-128-CBC(固定IV)" 174 | }, 175 | "cryptoLegacyDesc": 176 | { 177 | "message":"使用CryptoJS加密算法,会动态生成IV" 178 | }, 179 | "cryptoAesCbcFixedDesc": 180 | { 181 | "message":"使用标准 AES-128-CBC 算法,IV固定为 0x0" 182 | } 183 | } -------------------------------------------------------------------------------- /ext/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/128.png -------------------------------------------------------------------------------- /ext/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/16.png -------------------------------------------------------------------------------- /ext/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/32.png -------------------------------------------------------------------------------- /ext/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/48.png -------------------------------------------------------------------------------- /ext/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/96.png -------------------------------------------------------------------------------- /ext/public/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/ext/public/icon/icon.png -------------------------------------------------------------------------------- /ext/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./entrypoints/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | "./utils/**/*.{js,ts,jsx,tsx}" 7 | ], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | } -------------------------------------------------------------------------------- /ext/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "types": ["webextension-polyfill"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ext/utils/messaging.ts: -------------------------------------------------------------------------------- 1 | import { upload_cookie, download_cookie } from './functions'; 2 | 3 | export interface RequestBody { 4 | payload: any; 5 | } 6 | 7 | export interface ResponseBody { 8 | message: string; 9 | note: string | null; 10 | } 11 | 12 | export async function handleConfigMessage(payload: any): Promise { 13 | const result = (payload.type && payload.type == 'down') ? 14 | await download_cookie(payload) : 15 | await upload_cookie(payload); 16 | 17 | return { 18 | message: result.action, 19 | note: result.note || null, 20 | }; 21 | } -------------------------------------------------------------------------------- /ext/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'wxt'; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ['@wxt-dev/webextension-polyfill'], 6 | manifest: { 7 | name: '__MSG_appTitle__', 8 | description: '__MSG_appDesc__', 9 | default_locale: 'zh_CN', 10 | permissions: [ 11 | 'cookies', 12 | 'tabs', 13 | 'storage', 14 | 'alarms', 15 | 'unlimitedStorage' 16 | ], 17 | host_permissions: [ 18 | '' 19 | ] 20 | }, 21 | vite: () => ({ 22 | css: { 23 | postcss: './postcss.config.js' 24 | } 25 | }) 26 | }); 27 | -------------------------------------------------------------------------------- /extension/.github/workflows/submit.yml: -------------------------------------------------------------------------------- 1 | name: "Submit to Web Store" 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Cache pnpm modules 11 | uses: actions/cache@v3 12 | with: 13 | path: ~/.pnpm-store 14 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 15 | restore-keys: | 16 | ${{ runner.os }}- 17 | - uses: pnpm/action-setup@v2.2.4 18 | with: 19 | version: latest 20 | run_install: true 21 | - name: Use Node.js 16.x 22 | uses: actions/setup-node@v3.4.1 23 | with: 24 | node-version: 16.x 25 | cache: "pnpm" 26 | - name: Build and zip extension artifact 27 | run: pnpm package 28 | - name: Browser Platform Publish 29 | uses: PlasmoHQ/bpp@v3 30 | with: 31 | keys: ${{ secrets.SUBMIT_KEYS }} 32 | artifact: build/chrome-mv3-prod.zip 33 | -------------------------------------------------------------------------------- /extension/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | #cache 13 | .turbo 14 | .next 15 | .vercel 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .pnpm-debug.log* 26 | 27 | 28 | # local env files 29 | .env* 30 | 31 | out/ 32 | build/ 33 | dist/ 34 | 35 | # plasmo - https://www.plasmo.com 36 | .plasmo 37 | 38 | # bpp - http://bpp.browser.market/ 39 | keys.json 40 | 41 | # typescript 42 | .tsbuildinfo 43 | -------------------------------------------------------------------------------- /extension/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | module.exports = { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: [require.resolve("@plasmohq/prettier-plugin-sort-imports")], 14 | importOrder: ["^@plasmohq/(.*)$", "^~(.*)$", "^[./]"], 15 | importOrderSeparation: true, 16 | importOrderSortSpecifiers: true 17 | } 18 | -------------------------------------------------------------------------------- /extension/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /extension/README.md: -------------------------------------------------------------------------------- 1 | This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | pnpm dev 9 | # or 10 | npm run dev 11 | ``` 12 | 13 | Open your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. 14 | 15 | You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser. 16 | 17 | For further guidance, [visit our Documentation](https://docs.plasmo.com/) 18 | 19 | ## Making production build 20 | 21 | Run the following: 22 | 23 | ```bash 24 | pnpm build 25 | # or 26 | npm run build 27 | ``` 28 | 29 | This should create a production bundle for your extension, ready to be zipped and published to the stores. 30 | 31 | ## Submit to the webstores 32 | 33 | The easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://bpp.browser.market) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission! 34 | -------------------------------------------------------------------------------- /extension/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easychen/CookieCloud/79bea92a84cdf65a12308c187d8628089317ba4e/extension/assets/icon.png -------------------------------------------------------------------------------- /extension/background/index.ts: -------------------------------------------------------------------------------- 1 | import "@plasmohq/messaging/background"; 2 | import { upload_cookie, download_cookie, load_data, save_data, sleep } from '../function'; 3 | import browser from 'webextension-polyfill'; 4 | 5 | export const life = 42 6 | console.log(`HELLO WORLD - ${life}`) 7 | 8 | browser.runtime.onInstalled.addListener(function (details) 9 | { 10 | if (details.reason == "install") 11 | { 12 | browser.alarms.create('bg_1_minute', 13 | { 14 | when: Date.now(), 15 | periodInMinutes: 1 16 | }); 17 | } 18 | else if (details.reason == "update") 19 | { 20 | browser.alarms.create('bg_1_minute', 21 | { 22 | when: Date.now(), 23 | periodInMinutes: 1 24 | }); 25 | } 26 | }); 27 | 28 | 29 | 30 | browser.alarms.onAlarm.addListener( async a => 31 | { 32 | if( a.name == 'bg_1_minute' ) 33 | { 34 | // console.log( 'bg_1_minute' ); 35 | const config = await load_data("COOKIE_SYNC_SETTING") ; 36 | if( config ) 37 | { 38 | if( config.type && config.type == 'pause') 39 | { 40 | console.log("暂停模式,不同步"); 41 | return true; 42 | } 43 | 44 | // 获得当前的分钟数 45 | const now = new Date(); 46 | const minute = now.getMinutes(); 47 | const hour = now.getHours(); 48 | const day = now.getDate(); 49 | const minute_count = ( day*24 + hour)*60 + minute; 50 | 51 | if( config.uuid ) 52 | { 53 | // 如果时间间隔可以整除分钟数,则进行同步 54 | if( parseInt(config.interval) < 1 || minute_count % config.interval == 0 ) 55 | { 56 | // 开始同步 57 | console.log(`同步时间到 ${minute_count} ${config.interval}`); 58 | if(config.type && config.type == 'down') 59 | { 60 | // 从服务器取得cookie,向本地写入cookie 61 | const result = await download_cookie(config); 62 | if( result && result['action'] == 'done' ) 63 | console.log("下载覆盖成功"); 64 | else 65 | console.log( result ); 66 | }else 67 | { 68 | 69 | const result = await upload_cookie(config); 70 | if( result && result['action'] == 'done' ) 71 | console.log("上传成功"); 72 | else 73 | console.log( result ); 74 | } 75 | }else 76 | { 77 | // console.log(`未到同步时间 ${minute_count} ${config.interval}`); 78 | } 79 | } 80 | 81 | if( config.keep_live ) 82 | { 83 | // 按行分割,每行的格式为 url|interval 84 | const keep_live = config.keep_live?.trim()?.split("\n"); 85 | for( let i=0; i 0 && minute_count % interval == 0 ) 92 | { 93 | // 开始访问 94 | console.log(`keep live ${url} ${minute_count} ${interval}`); 95 | 96 | // 查询是否已经打开目标页面,如果已经打开,则不再打开 97 | // 除了没有必要以外,还能避免因为网络延迟导致的重复打开 98 | const [exists_tab] = await browser.tabs.query({"url":`${url.trim().replace(/\/+$/, '')}/*`}); 99 | if( exists_tab && exists_tab.id ) 100 | { 101 | console.log(`tab已存在 ${exists_tab.id}`,exists_tab); 102 | if( !exists_tab.active ) 103 | { 104 | // refresh tab 105 | console.log(`后台状态,刷新页面`); 106 | await browser.tabs.reload(exists_tab.id); 107 | }else 108 | { 109 | console.log(`前台状态,跳过`); 110 | } 111 | return true; 112 | }else 113 | { 114 | console.log(`tab不存在,后台打开`); 115 | } 116 | 117 | // chrome tab create 118 | const tab = await browser.tabs.create({"url":url,"active":false,"pinned":true}); 119 | // 等待五秒后关闭 120 | await sleep(5000); 121 | await browser.tabs.remove(tab.id); 122 | } 123 | } 124 | } 125 | 126 | 127 | 128 | 129 | 130 | 131 | } 132 | 133 | 134 | } 135 | }); -------------------------------------------------------------------------------- /extension/background/messages/config.ts: -------------------------------------------------------------------------------- 1 | import type { PlasmoMessaging } from "@plasmohq/messaging" 2 | import { upload_cookie, download_cookie } from '../../function'; 3 | // import { Base64 } from 'js-base64'; 4 | 5 | export type RequestBody = { 6 | payload: object 7 | } 8 | 9 | export type ResponseBody = { 10 | message: string, 11 | note: string|null, 12 | } 13 | 14 | export const handler: PlasmoMessaging.MessageHandler = async (req, res) => { 16 | // 获得cookie,并进行过滤 17 | const payload = req.body.payload; 18 | const result = (payload['type'] && payload['type'] == 'down') ? await download_cookie(payload) : await upload_cookie(payload); 19 | res.send({ 20 | message: result['action'], 21 | note: result['note'], 22 | }) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /extension/content.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | import { load_data, save_data, remove_data, get_local_storage_by_domains } from './function'; 3 | 4 | 5 | window.addEventListener("load", async () => { 6 | 7 | // 获得当前域名 8 | const host = window.location.hostname; 9 | const config = await load_data("COOKIE_SYNC_SETTING") ; 10 | if( config?.domains ) 11 | { 12 | const domains = config.domains?.trim().split("\n"); 13 | // 检查 domain 是否部分匹配 domains的每一个域名 14 | let matched = false; 15 | for(const domain of domains) 16 | { 17 | if( host.includes(domain) ) matched = true; 18 | } 19 | if( domains.length > 0 && !matched ) return false; 20 | } 21 | 22 | if( config?.type && config.type == 'down' ) 23 | { 24 | const the_data = await load_data("LS-"+host); 25 | // console.log( "the_data", the_data ); 26 | if( the_data ) 27 | { 28 | // 覆盖本地的localStorage 29 | for( const key in the_data ) 30 | { 31 | localStorage.setItem(key,the_data[key]); 32 | } 33 | // 清空浏览器的storage,避免多次覆盖 34 | await remove_data("LS-"+host); 35 | } 36 | }else 37 | { 38 | // 获得当前页面全部的localStorage 39 | const all = localStorage; 40 | const keys = Object.keys(all); 41 | const values = Object.values(all); 42 | const result = {}; 43 | for( let i = 0; i < keys.length; i++ ) 44 | { 45 | result[keys[i]] = values[i]; 46 | } 47 | // 将其存储到浏览器的storage中 48 | if( Object.keys(result).length > 0 ) 49 | { 50 | await save_data("LS-"+host,result); 51 | console.log("save to storage",host, result); 52 | // console.log( (await get_local_storage_by_domains(['tqq']))); 53 | } 54 | } 55 | 56 | 57 | 58 | }) -------------------------------------------------------------------------------- /extension/locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "CookieCloud" 4 | }, 5 | "appDesc": { 6 | "message": "CookieCloud is a tool for syncing browser cookies to a self-hosted server.", 7 | "description": "Uses end-to-end encryption to ensure your cookies are secure." 8 | }, 9 | "test": { 10 | "message": "Test" 11 | }, 12 | "fullMessagePlease": { 13 | "message": "Please fill in all the information" 14 | }, 15 | "actionNotAllowedInPause": { 16 | "message": "This action cannot be performed while paused" 17 | }, 18 | "success": { 19 | "message": "Success" 20 | }, 21 | "failedCheckInfo": { 22 | "message": "Failed, please check if the information entered is correct" 23 | }, 24 | "saveSucess": { 25 | "message": "Save Successful" 26 | }, 27 | "workingMode": { 28 | "message": "Mode" 29 | }, 30 | "upToServer": { 31 | "message": "Upload to Server" 32 | }, 33 | "overwriteToBrowser": { 34 | "message": "Overwrite to Browser" 35 | }, 36 | "pauseSync": { 37 | "message": "Pause Sync" 38 | }, 39 | "overwriteModeDesp": { 40 | "message": "Overwrite mode is mainly used for cloud and read-only browsers. Overwriting Cookies and Local Storage may cause current browser login and modification operations to fail; moreover, some websites do not allow the same cookie to be logged in on multiple browsers at the same time, which may cause account logouts on other browsers." 41 | }, 42 | "serverHost": { 43 | "message": "Server Address" 44 | }, 45 | "serverHostPlaceholder": { 46 | "message": "Please enter server address" 47 | }, 48 | "uuid": { 49 | "message": "User KEY · UUID" 50 | }, 51 | "uuidPlaceholder": { 52 | "message": "Unique User ID" 53 | }, 54 | "reGenerate": { 55 | "message": "Regenerate" 56 | }, 57 | "syncPassword": { 58 | "message": "End-to-End Encryption Password" 59 | }, 60 | "syncPasswordPlaceholder": { 61 | "message": "Data will be unable to be decrypted if lost" 62 | }, 63 | "generate": { 64 | "message": "Generate" 65 | }, 66 | "cookieExpireMinutes": { 67 | "message": "Cookie Expiry Time · Minutes" 68 | }, 69 | "cookieExpireMinutesPlaceholder": { 70 | "message": "0 means it expires immediately after closing the browser" 71 | }, 72 | "syncTimeInterval": { 73 | "message": "Sync Interval · Minutes" 74 | }, 75 | "syncTimeIntervalPlaceholder": { 76 | "message": "At least 10 minutes" 77 | }, 78 | "syncLocalStorageOrNot": { 79 | "message": "Sync Local Storage?" 80 | }, 81 | "yes": { 82 | "message": "Yes" 83 | }, 84 | "no": { 85 | "message": "No" 86 | }, 87 | "requestHeader": { 88 | "message": "Request Header · Optional" 89 | }, 90 | "requestHeaderPlaceholder": { 91 | "message": "Append Header to request for server authentication etc., one per line, format as 'Key:Value', no spaces allowed" 92 | }, 93 | "syncDomainKeyword": { 94 | "message": "Sync Domain Keyword · Optional" 95 | }, 96 | "syncDomainKeywordPlaceholder": { 97 | "message": "One per line, sync all domains containing the keyword, e.g., qq.com, jd.com will include all subdomains, leave blank to sync all by default" 98 | }, 99 | "syncDomainBlacklist": { 100 | "message": "Sync Domain Blacklist · Optional" 101 | }, 102 | "syncDomainBlacklistPlaceholder": { 103 | "message": "The blacklist only takes effect when the sync domain keyword is empty. One domain per line, matching domains will not be synced" 104 | }, 105 | "cookieKeepLive": { 106 | "message": "Cookie Keep Alive · Optional" 107 | }, 108 | "cookieKeepLivePlaceholder": { 109 | "message": "Periodically refresh URL in the background to simulate user activity. One URL per line, default 60 minutes, can specify refresh time with URL|minutes format" 110 | }, 111 | "keepLiveStop": { 112 | "message": "Pause Sync and Keep Alive" 113 | }, 114 | "syncManual": { 115 | "message": "Manual Sync" 116 | }, 117 | "save": { 118 | "message": "Save" 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /extension/locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "message": "CookieCloud" 4 | }, 5 | "appDesc": { 6 | "message": "CookieCloud是一款向自架服务器同步浏览器Cookie的工具。", 7 | "description":"采用端对端加密,保证您的Cookie安全。" 8 | }, 9 | "test": 10 | { 11 | "message":"测试" 12 | }, 13 | "fullMessagePlease": 14 | { 15 | "message":"请填写完整的信息" 16 | }, 17 | "actionNotAllowedInPause": 18 | { 19 | "message":"暂停状态下无法进行此操作" 20 | }, 21 | "success": 22 | { 23 | "message":"成功" 24 | }, 25 | "failedCheckInfo": 26 | { 27 | "message":"失败,请检查填写的信息是否正确" 28 | }, 29 | "saveSucess": 30 | { 31 | "message":"保存成功" 32 | }, 33 | "workingMode": 34 | { 35 | "message":"工作模式" 36 | }, 37 | "upToServer":{ 38 | "message":"上传到服务器" 39 | }, 40 | "overwriteToBrowser":{ 41 | "message":"覆盖到浏览器" 42 | }, 43 | "pauseSync": 44 | { 45 | "message":"暂停同步" 46 | }, 47 | "overwriteModeDesp": 48 | { 49 | "message":"覆盖模式主要用于云端和只读用的浏览器,Cookie和Local Storage覆盖可能导致当前浏览器的登录和修改操作失效;另外部分网站不允许同一个cookie在多个浏览器同时登录,可能导致其他浏览器上账号退出。" 50 | }, 51 | "serverHost": 52 | { 53 | "message":"服务器地址" 54 | }, 55 | "serverHostPlaceholder": 56 | { 57 | "message":"请输入服务器地址" 58 | }, 59 | "uuid": 60 | { 61 | "message":"用户KEY · UUID" 62 | }, 63 | "uuidPlaceholder": 64 | { 65 | "message":"唯一用户ID" 66 | }, 67 | "reGenerate": 68 | { 69 | "message":"重新生成" 70 | }, 71 | "syncPassword": 72 | { 73 | "message":"端对端加密密码" 74 | }, 75 | "syncPasswordPlaceholder": 76 | { 77 | "message":"丢失后数据失效,请妥善保管" 78 | }, 79 | "generate": 80 | { 81 | "message":"自动生成" 82 | }, 83 | "cookieExpireMinutes": 84 | { 85 | "message":"Cookie过期时间·分钟" 86 | }, 87 | "cookieExpireMinutesPlaceholder": 88 | { 89 | "message":"0为关闭浏览器后立刻过期" 90 | }, 91 | "syncTimeInterval": 92 | { 93 | "message":"同步时间间隔·分钟" 94 | }, 95 | "syncTimeIntervalPlaceholder": 96 | { 97 | "message":"最少10分钟" 98 | }, 99 | "syncLocalStorageOrNot": 100 | { 101 | "message":"是否同步Local Storage" 102 | }, 103 | "yes": 104 | { 105 | "message":"是" 106 | }, 107 | "no": 108 | { 109 | "message":"否" 110 | }, 111 | "requestHeader": 112 | { 113 | "message":"请求Header·选填" 114 | }, 115 | "requestHeaderPlaceholder": 116 | { 117 | "message":"在请求时追加Header,用于服务端鉴权等场景,一行一个,格式为'Key:Value',不能有空格" 118 | }, 119 | "syncDomainKeyword": 120 | { 121 | "message":"同步域名关键词·选填" 122 | }, 123 | "syncDomainKeywordPlaceholder": 124 | { 125 | "message":"一行一个,同步包含关键词的全部域名,如qq.com,jd.com会包含全部子域名,留空默认同步全部" 126 | }, 127 | "syncDomainBlacklist": 128 | { 129 | "message":"同步域名黑名单·选填" 130 | }, 131 | "syncDomainBlacklistPlaceholder": 132 | { 133 | "message":"黑名单仅在同步域名关键词为空时生效。一行一个域名,匹配则不参与同步" 134 | }, 135 | "cookieKeepLive": 136 | { 137 | "message":"Cookie保活·选填" 138 | }, 139 | "cookieKeepLivePlaceholder": 140 | { 141 | "message":"定时后台刷新URL,模拟用户活跃。一行一个URL,默认60分钟,可用 URL|分钟数 的方式指定刷新时间" 142 | }, 143 | "keepLiveStop": 144 | { 145 | "message":"暂停同步和保活" 146 | }, 147 | "syncManual": 148 | { 149 | "message":"手动同步" 150 | }, 151 | "save": 152 | { 153 | "message":"保存" 154 | } 155 | } -------------------------------------------------------------------------------- /extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookie-cloud", 3 | "displayName": "CookieCloud", 4 | "description": "__MSG_appDesc__", 5 | "version": "0.2.4", 6 | "default_locale": "zh_CN", 7 | "author": "easychen@gmail.com", 8 | "license": "GPLv3", 9 | "scripts": { 10 | "dev": "plasmo dev", 11 | "build": "plasmo build", 12 | "package": "plasmo package", 13 | "firefox": "plasmo build --target=firefox-mv2" 14 | }, 15 | "dependencies": { 16 | "@plasmohq/messaging": "^0.0.2", 17 | "crypto-js": "^4.2.0", 18 | "js-base64": "^3.7.6", 19 | "pako": "^2.1.0", 20 | "plasmo": "0.62.2", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "sharp": "^0.33.2", 24 | "short-uuid": "^4.2.2", 25 | "webextension-polyfill": "^0.10.0" 26 | }, 27 | "devDependencies": { 28 | "@plasmohq/prettier-plugin-sort-imports": "3.6.0", 29 | "@types/chrome": "0.0.202", 30 | "@types/node": "18.11.9", 31 | "@types/react": "18.0.25", 32 | "@types/react-dom": "18.0.9", 33 | "autoprefixer": "^10.4.17", 34 | "postcss": "^8.4.35", 35 | "prettier": "2.7.1", 36 | "tailwindcss": "^3.4.1", 37 | "typescript": "4.9.3" 38 | }, 39 | "manifest": { 40 | "name": "__MSG_appTitle__", 41 | "default_locale": "zh_CN", 42 | "host_permissions": [ 43 | "" 44 | ], 45 | "permissions": [ 46 | "cookies", 47 | "tabs", 48 | "storage", 49 | "alarms", 50 | "unlimitedStorage" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /extension/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __plasmo_static_index_title__ 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extension/popup.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import { sendToBackground } from "@plasmohq/messaging" 3 | import type { RequestBody, ResponseBody } from "~background/messages/config" 4 | import short_uid from 'short-uuid'; 5 | import "./style.scss" 6 | import { load_data, save_data } from './function'; 7 | import browser from 'webextension-polyfill'; 8 | 9 | function IndexPopup() { 10 | let init: Object={"endpoint":"http://127.0.0.1:8088","password":"","interval":10,"domains":"","uuid":String(short_uid.generate()),"type":"up","keep_live":"","with_storage":1,"blacklist":"google.com", "headers": "","expire_minutes":60*24*365}; 11 | const [data, setData] = useState(init); 12 | 13 | async function test(action=browser.i18n.getMessage('test')) 14 | { 15 | console.log("request,begin"); 16 | if( !data['endpoint'] || !data['password'] || !data['uuid'] || !data['type'] ) 17 | { 18 | alert(browser.i18n.getMessage("fullMessagePlease")); 19 | return; 20 | } 21 | if( data['type'] == 'pause' ) 22 | { 23 | // alert('暂停状态不能'+action); 24 | alert(browser.i18n.getMessage("actionNotAllowedInPause")); 25 | return; 26 | } 27 | const ret = await sendToBackground({name:"config",body:{payload:{...data,no_cache:1}}}); 28 | console.log(action+"返回",ret); 29 | if( ret && ret['message'] == 'done' ) 30 | { 31 | if( ret['note'] ) 32 | alert(ret['note']); 33 | else 34 | alert(action+browser.i18n.getMessage('success')); 35 | }else 36 | { 37 | alert(action+browser.i18n.getMessage('failedCheckInfo')); 38 | } 39 | } 40 | 41 | async function save() 42 | { 43 | if( !data['endpoint'] || !data['password'] || !data['uuid'] || !data['type'] ) 44 | { 45 | // alert('请填写完整的信息'); 46 | alert(browser.i18n.getMessage("fullMessagePlease")); 47 | return; 48 | } 49 | await save_data( "COOKIE_SYNC_SETTING", data ); 50 | const ret = await load_data("COOKIE_SYNC_SETTING") ; 51 | console.log( "load", ret ); 52 | if( JSON.stringify(ret) == JSON.stringify(data) ) 53 | { 54 | // alert('保存成功'); 55 | alert(browser.i18n.getMessage("saveSuccess")); 56 | window.close(); 57 | } 58 | } 59 | 60 | function onChange(name:string, e:(React.ChangeEvent)) 61 | { 62 | // console.log( "e" , name , e.target.value ); 63 | setData({...data,[name]:e.target.value??''}); 64 | } 65 | 66 | function uuid_regen() 67 | { 68 | setData({...data,'uuid':String(short_uid.generate())}); 69 | } 70 | 71 | function password_gen() 72 | { 73 | setData({...data,'password':String(short_uid.generate())}); 74 | } 75 | 76 | useEffect(() => { 77 | async function load_config() 78 | { 79 | const ret = await load_data("COOKIE_SYNC_SETTING") ; 80 | if( ret ) setData({...data,...ret}); 81 | } 82 | load_config(); 83 | },[]); 84 | 85 | return
86 |
87 |
88 |
{browser.i18n.getMessage('workingMode')}
89 |
90 | {/* 91 | onChange('type',e)} value={data['type']}> 92 | 上传到服务器 93 | 覆盖到浏览器 94 | 暂停 95 | 96 | */} 97 | 98 | 100 | 101 | 102 |
103 | 104 | {data['type'] && data['type'] == 'down' &&
105 | {browser.i18n.getMessage('overwriteModeDesp')} 106 |
} 107 | 108 | {data['type'] && data['type'] != 'pause' && <> 109 |
{browser.i18n.getMessage('serverHost')}
110 | onChange('endpoint',e)} /> 111 |
{browser.i18n.getMessage('uuid')}
112 |
113 |
114 | onChange('uuid',e)}/> 115 |
116 |
117 | 118 |
119 |
120 |
{browser.i18n.getMessage('syncPassword')}
121 |
122 |
123 | onChange('password',e)}/> 124 |
125 |
126 | 127 |
128 |
129 |
{browser.i18n.getMessage('cookieExpireMinutes')}
130 | onChange('expire_minutes',e)} /> 131 | 132 |
{browser.i18n.getMessage('syncTimeInterval')}
133 | onChange('interval',e)} /> 134 | 135 | {data['type'] && data['type'] == 'up' && <> 136 |
{browser.i18n.getMessage('syncLocalStorageOrNot')}
137 |
138 | {/* 139 | onChange('with_storage',e)} value={data['with_storage']}> 140 | 141 | 142 | 143 | */} 144 | 145 | 146 |
147 | 148 |
{browser.i18n.getMessage('requestHeader')}
149 |