diff --git a/build-clang/Testing/Temporary/CTestCostData.txt b/build-clang/Testing/Temporary/CTestCostData.txt index 4bd7b77..b5cbb2c 100644 --- a/build-clang/Testing/Temporary/CTestCostData.txt +++ b/build-clang/Testing/Temporary/CTestCostData.txt @@ -1,3 +1,3 @@ -test_basic 13 0.51498 +test_basic 14 0.474679 example_run 0 0 --- diff --git a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/base58.cpp.html b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/base58.cpp.html index 8f26197..f164bca 100644 --- a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/base58.cpp.html +++ b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/base58.cpp.html @@ -1 +1 @@ -
Line | Count | Source |
1 | #include "base58.h" | |
2 | #include <algorithm> | |
3 | #include <stdexcept> | |
4 | ||
5 | const std::string Base58::ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
6 | ||
7 | 0 | std::string Base58::encode(const std::vector<unsigned char>& data) { |
8 | 0 | if (data.empty()) { |
9 | 0 | return ""; |
10 | 0 | } |
11 | ||
12 | // Skip leading zeros | |
13 | 0 | size_t leading_zeros = 0; |
14 | 0 | while (leading_zeros < data.size() && data[leading_zeros] == 0) { |
15 | 0 | leading_zeros++; |
16 | 0 | } |
17 | ||
18 | // Convert to base58 | |
19 | 0 | size_t digits_size = (data.size() - leading_zeros) * 138 / 100 + 1; |
20 | 0 | std::vector<unsigned char> digits(digits_size); |
21 | 0 | size_t digitslen = 1; |
22 | ||
23 | 0 | for (size_t i = leading_zeros; i < data.size(); i++) { |
24 | 0 | size_t carry = static_cast<size_t>(data[i]); |
25 | 0 | for (size_t j = 0; j < digitslen; j++) { |
26 | 0 | carry += static_cast<size_t>(digits[j]) << 8; |
27 | 0 | digits[j] = static_cast<unsigned char>(carry % 58); |
28 | 0 | carry /= 58; |
29 | 0 | } |
30 | 0 | while (carry > 0) { |
31 | 0 | digits[digitslen++] = static_cast<unsigned char>(carry % 58); |
32 | 0 | carry /= 58; |
33 | 0 | } |
34 | 0 | } |
35 | ||
36 | // Convert to string | |
37 | 0 | std::string result; |
38 | 0 | for (size_t i = 0; i < leading_zeros; i++) { |
39 | 0 | result += ALPHABET[0]; |
40 | 0 | } |
41 | 0 | for (size_t i = 0; i < digitslen; i++) { |
42 | 0 | result += ALPHABET[digits[digitslen - 1 - i]]; |
43 | 0 | } |
44 | ||
45 | 0 | return result; |
46 | 0 | } |
47 | ||
48 | 0 | std::vector<unsigned char> Base58::decode(const std::string& encoded) { |
49 | 0 | if (encoded.empty()) { |
50 | 0 | return std::vector<unsigned char>(); |
51 | 0 | } |
52 | ||
53 | // Skip leading '1's (which represent leading zeros) | |
54 | 0 | size_t leading_ones = 0; |
55 | 0 | while (leading_ones < encoded.length() && encoded[leading_ones] == '1') { |
56 | 0 | leading_ones++; |
57 | 0 | } |
58 | ||
59 | // Convert from base58 | |
60 | 0 | size_t bytes_size = (encoded.length() - leading_ones) * 733 / 1000 + 1; |
61 | 0 | std::vector<unsigned char> bytes(bytes_size); |
62 | 0 | size_t byteslen = 1; |
63 | ||
64 | 0 | for (size_t i = leading_ones; i < encoded.length(); i++) { |
65 | 0 | size_t carry = static_cast<size_t>(ALPHABET.find(encoded[i])); |
66 | 0 | if (carry == std::string::npos) { |
67 | 0 | throw std::invalid_argument("Invalid character in Base58 string"); |
68 | 0 | } |
69 | ||
70 | 0 | for (size_t j = 0; j < byteslen; j++) { |
71 | 0 | carry += static_cast<size_t>(bytes[j]) * 58; |
72 | 0 | bytes[j] = static_cast<unsigned char>(carry & 0xff); |
73 | 0 | carry >>= 8; |
74 | 0 | } |
75 | 0 | while (carry > 0) { |
76 | 0 | bytes[byteslen++] = static_cast<unsigned char>(carry & 0xff); |
77 | 0 | carry >>= 8; |
78 | 0 | } |
79 | 0 | } |
80 | ||
81 | // Add leading zeros | |
82 | 0 | std::vector<unsigned char> result(leading_ones + byteslen); |
83 | 0 | for (size_t i = 0; i < leading_ones; i++) { |
84 | 0 | result[i] = 0; |
85 | 0 | } |
86 | 0 | for (size_t i = 0; i < byteslen; i++) { |
87 | 0 | result[leading_ones + i] = bytes[byteslen - 1 - i]; |
88 | 0 | } |
89 | ||
90 | 0 | return result; |
91 | 0 | } |
Line | Count | Source |
1 | #include "base58.h" | |
2 | #include <algorithm> | |
3 | #include <stdexcept> | |
4 | ||
5 | const std::string Base58::ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
6 | ||
7 | 0 | std::string Base58::encode(const std::vector<unsigned char>& data) { |
8 | 0 | if (data.empty()) { |
9 | 0 | return ""; |
10 | 0 | } |
11 | ||
12 | // Skip leading zeros | |
13 | 0 | size_t leading_zeros = 0; |
14 | 0 | while (leading_zeros < data.size() && data[leading_zeros] == 0) { |
15 | 0 | leading_zeros++; |
16 | 0 | } |
17 | ||
18 | // Convert to base58 | |
19 | 0 | size_t digits_size = (data.size() - leading_zeros) * 138 / 100 + 1; |
20 | 0 | std::vector<unsigned char> digits(digits_size); |
21 | 0 | size_t digitslen = 1; |
22 | ||
23 | 0 | for (size_t i = leading_zeros; i < data.size(); i++) { |
24 | 0 | size_t carry = static_cast<size_t>(data[i]); |
25 | 0 | for (size_t j = 0; j < digitslen; j++) { |
26 | 0 | carry += static_cast<size_t>(digits[j]) << 8; |
27 | 0 | digits[j] = static_cast<unsigned char>(carry % 58); |
28 | 0 | carry /= 58; |
29 | 0 | } |
30 | 0 | while (carry > 0) { |
31 | 0 | digits[digitslen++] = static_cast<unsigned char>(carry % 58); |
32 | 0 | carry /= 58; |
33 | 0 | } |
34 | 0 | } |
35 | ||
36 | // Convert to string | |
37 | 0 | std::string result; |
38 | 0 | for (size_t i = 0; i < leading_zeros; i++) { |
39 | 0 | result += ALPHABET[0]; |
40 | 0 | } |
41 | 0 | for (size_t i = 0; i < digitslen; i++) { |
42 | 0 | result += ALPHABET[digits[digitslen - 1 - i]]; |
43 | 0 | } |
44 | ||
45 | 0 | return result; |
46 | 0 | } |
47 | ||
48 | 0 | std::vector<unsigned char> Base58::decode(const std::string& encoded) { |
49 | 0 | if (encoded.empty()) { |
50 | 0 | return std::vector<unsigned char>(); |
51 | 0 | } |
52 | ||
53 | // Skip leading '1's (which represent leading zeros) | |
54 | 0 | size_t leading_ones = 0; |
55 | 0 | while (leading_ones < encoded.length() && encoded[leading_ones] == '1') { |
56 | 0 | leading_ones++; |
57 | 0 | } |
58 | ||
59 | // Convert from base58 | |
60 | 0 | size_t bytes_size = (encoded.length() - leading_ones) * 733 / 1000 + 1; |
61 | 0 | std::vector<unsigned char> bytes(bytes_size); |
62 | 0 | size_t byteslen = 1; |
63 | ||
64 | 0 | for (size_t i = leading_ones; i < encoded.length(); i++) { |
65 | 0 | size_t carry = static_cast<size_t>(ALPHABET.find(encoded[i])); |
66 | 0 | if (carry == std::string::npos) { |
67 | 0 | throw std::invalid_argument("Invalid character in Base58 string"); |
68 | 0 | } |
69 | ||
70 | 0 | for (size_t j = 0; j < byteslen; j++) { |
71 | 0 | carry += static_cast<size_t>(bytes[j]) * 58; |
72 | 0 | bytes[j] = static_cast<unsigned char>(carry & 0xff); |
73 | 0 | carry >>= 8; |
74 | 0 | } |
75 | 0 | while (carry > 0) { |
76 | 0 | bytes[byteslen++] = static_cast<unsigned char>(carry & 0xff); |
77 | 0 | carry >>= 8; |
78 | 0 | } |
79 | 0 | } |
80 | ||
81 | // Add leading zeros | |
82 | 0 | std::vector<unsigned char> result(leading_ones + byteslen); |
83 | 0 | for (size_t i = 0; i < leading_ones; i++) { |
84 | 0 | result[i] = 0; |
85 | 0 | } |
86 | 0 | for (size_t i = 0; i < byteslen; i++) { |
87 | 0 | result[leading_ones + i] = bytes[byteslen - 1 - i]; |
88 | 0 | } |
89 | ||
90 | 0 | return result; |
91 | 0 | } |
Line | Count | Source |
1 | #include "crypto.h" | |
2 | #include <random> | |
3 | #include <stdexcept> | |
4 | #include <cstring> | |
5 | ||
6 | // Crypto++ includes | |
7 | #include <cryptopp/cryptlib.h> | |
8 | #include <cryptopp/osrng.h> // AutoSeededRandomPool | |
9 | #include <cryptopp/aes.h> // AES encryption | |
10 | #include <cryptopp/gcm.h> // GCM mode | |
11 | #include <cryptopp/pwdbased.h> // PBKDF2 | |
12 | #include <cryptopp/sha.h> // SHA256 | |
13 | #include <cryptopp/zlib.h> // Zlib compression | |
14 | ||
15 | using namespace CryptoPP; | |
16 | ||
17 | 3 | std::vector<unsigned char> Crypto::generate_key(size_t length) { |
18 | 3 | std::vector<unsigned char> key(length); |
19 | ||
20 | // Use Crypto++ AutoSeededRandomPool for cryptographically secure random numbers | |
21 | 3 | AutoSeededRandomPool rng; |
22 | 3 | rng.GenerateBlock(key.data(), length); |
23 | ||
24 | 3 | return key; |
25 | 3 | } |
26 | ||
27 | std::vector<unsigned char> Crypto::encrypt(const std::vector<unsigned char>& plaintext, | |
28 | const std::vector<unsigned char>& key, | |
29 | const std::vector<unsigned char>& iv, | |
30 | 1 | std::vector<unsigned char>& auth_tag) { |
31 | 1 | try { |
32 | // Create GCM mode encryption object | |
33 | 1 | GCM<AES>::Encryption encryption; |
34 | ||
35 | // Set key and IV | |
36 | 1 | encryption.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size()); |
37 | ||
38 | // Prepare output vector | |
39 | 1 | std::vector<unsigned char> ciphertext(plaintext.size()); |
40 | ||
41 | // Prepare authentication tag (16 bytes for GCM) | |
42 | 1 | auth_tag.resize(16); |
43 | ||
44 | // Perform encryption | |
45 | 1 | encryption.EncryptAndAuthenticate( |
46 | 1 | ciphertext.data(), |
47 | 1 | auth_tag.data(), |
48 | 1 | static_cast<int>(auth_tag.size()), |
49 | 1 | iv.data(), |
50 | 1 | static_cast<int>(iv.size()), |
51 | 1 | nullptr, |
52 | 1 | 0, // Additional authenticated data |
53 | 1 | plaintext.data(), |
54 | 1 | static_cast<int>(plaintext.size()) |
55 | 1 | ); |
56 | ||
57 | 1 | return ciphertext; |
58 | 1 | } |
59 | 1 | catch(const CryptoPP::Exception& e) { |
60 | 0 | throw std::runtime_error("Encryption failed: " + std::string(e.what())); |
61 | 0 | } |
62 | 1 | } |
63 | ||
64 | std::vector<unsigned char> Crypto::decrypt(const std::vector<unsigned char>& ciphertext, | |
65 | const std::vector<unsigned char>& key, | |
66 | const std::vector<unsigned char>& iv, | |
67 | 0 | const std::vector<unsigned char>& auth_tag) { |
68 | 0 | try { |
69 | // Create GCM mode decryption object | |
70 | 0 | GCM<AES>::Decryption decryption; |
71 | ||
72 | // Set key and IV | |
73 | 0 | decryption.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size()); |
74 | ||
75 | // Prepare output vector | |
76 | 0 | std::vector<unsigned char> plaintext(ciphertext.size()); |
77 | ||
78 | // Perform decryption and authentication | |
79 | 0 | bool valid = decryption.DecryptAndVerify( |
80 | 0 | plaintext.data(), |
81 | 0 | auth_tag.data(), |
82 | 0 | static_cast<int>(auth_tag.size()), |
83 | 0 | iv.data(), |
84 | 0 | static_cast<int>(iv.size()), |
85 | 0 | nullptr, |
86 | 0 | 0, // Additional authenticated data |
87 | 0 | ciphertext.data(), |
88 | 0 | static_cast<int>(ciphertext.size()) |
89 | 0 | ); |
90 | ||
91 | 0 | if(!valid) { |
92 | 0 | throw std::runtime_error("Authentication failed during decryption"); |
93 | 0 | } |
94 | ||
95 | 0 | return plaintext; |
96 | 0 | } |
97 | 0 | catch(const CryptoPP::Exception& e) { |
98 | 0 | throw std::runtime_error("Decryption failed: " + std::string(e.what())); |
99 | 0 | } |
100 | 0 | } |
101 | ||
102 | std::vector<unsigned char> Crypto::pbkdf2_hmac_sha256(const std::string& password, | |
103 | const std::vector<unsigned char>& salt, | |
104 | int iterations, | |
105 | 1 | size_t key_length) { |
106 | 1 | try { |
107 | 1 | std::vector<unsigned char> derived_key(key_length); |
108 | ||
109 | // Use Crypto++ PKCS5_PBKDF2_HMAC for key derivation | |
110 | 1 | PKCS5_PBKDF2_HMAC<SHA256> pbkdf; |
111 | 1 | pbkdf.DeriveKey( |
112 | 1 | derived_key.data(), |
113 | 1 | derived_key.size(), |
114 | 1 | 0x00, // Purpose byte |
115 | 1 | reinterpret_cast<const byte*>(password.data()), |
116 | 1 | password.length(), |
117 | 1 | salt.data(), |
118 | 1 | salt.size(), |
119 | 1 | iterations, |
120 | 1 | 0.0f // Timeout (0 = no timeout) |
121 | 1 | ); |
122 | ||
123 | 1 | return derived_key; |
124 | 1 | } |
125 | 1 | catch(const CryptoPP::Exception& e) { |
126 | 0 | throw std::runtime_error("PBKDF2 key derivation failed: " + std::string(e.what())); |
127 | 0 | } |
128 | 1 | } |
129 | ||
130 | 1 | std::vector<unsigned char> Crypto::compress(const std::vector<unsigned char>& data) { |
131 | 1 | try { |
132 | 1 | std::string compressed; |
133 | ||
134 | // Create zlib compressor | |
135 | 1 | ZlibCompressor compressor; |
136 | 1 | compressor.Put(data.data(), data.size()); |
137 | 1 | compressor.MessageEnd(); |
138 | ||
139 | // Retrieve compressed data | |
140 | 1 | size_t size = compressor.MaxRetrievable(); |
141 | 1 | compressed.resize(size); |
142 | 1 | compressor.Get(reinterpret_cast<byte*>(&compressed[0]), compressed.size()); |
143 | ||
144 | // Convert to vector | |
145 | 1 | return std::vector<unsigned char>(compressed.begin(), compressed.end()); |
146 | 1 | } |
147 | 1 | catch(const CryptoPP::Exception& e) { |
148 | 0 | throw std::runtime_error("Compression failed: " + std::string(e.what())); |
149 | 0 | } |
150 | 1 | } |
151 | ||
152 | 0 | std::vector<unsigned char> Crypto::decompress(const std::vector<unsigned char>& data) { |
153 | 0 | try { |
154 | 0 | std::string decompressed; |
155 | ||
156 | // Create zlib decompressor | |
157 | 0 | ZlibDecompressor decompressor; |
158 | 0 | decompressor.Put(data.data(), data.size()); |
159 | 0 | decompressor.MessageEnd(); |
160 | ||
161 | // Retrieve decompressed data | |
162 | 0 | size_t size = decompressor.MaxRetrievable(); |
163 | 0 | decompressed.resize(size); |
164 | 0 | decompressor.Get(reinterpret_cast<byte*>(&decompressed[0]), decompressed.size()); |
165 | ||
166 | // Convert to vector | |
167 | 0 | return std::vector<unsigned char>(decompressed.begin(), decompressed.end()); |
168 | 0 | } |
169 | 0 | catch(const CryptoPP::Exception& e) { |
170 | 0 | throw std::runtime_error("Decompression failed: " + std::string(e.what())); |
171 | 0 | } |
172 | 0 | } |
Line | Count | Source |
1 | #include "crypto.h" | |
2 | #include <random> | |
3 | #include <stdexcept> | |
4 | #include <cstring> | |
5 | ||
6 | // Crypto++ includes | |
7 | #include <cryptopp/cryptlib.h> | |
8 | #include <cryptopp/osrng.h> // AutoSeededRandomPool | |
9 | #include <cryptopp/aes.h> // AES encryption | |
10 | #include <cryptopp/gcm.h> // GCM mode | |
11 | #include <cryptopp/pwdbased.h> // PBKDF2 | |
12 | #include <cryptopp/sha.h> // SHA256 | |
13 | #include <cryptopp/zlib.h> // Zlib compression | |
14 | ||
15 | using namespace CryptoPP; | |
16 | ||
17 | 3 | std::vector<unsigned char> Crypto::generate_key(size_t length) { |
18 | 3 | std::vector<unsigned char> key(length); |
19 | ||
20 | // Use Crypto++ AutoSeededRandomPool for cryptographically secure random numbers | |
21 | 3 | AutoSeededRandomPool rng; |
22 | 3 | rng.GenerateBlock(key.data(), length); |
23 | ||
24 | 3 | return key; |
25 | 3 | } |
26 | ||
27 | std::vector<unsigned char> Crypto::encrypt(const std::vector<unsigned char>& plaintext, | |
28 | const std::vector<unsigned char>& key, | |
29 | const std::vector<unsigned char>& iv, | |
30 | 1 | std::vector<unsigned char>& auth_tag) { |
31 | 1 | try { |
32 | // Create GCM mode encryption object | |
33 | 1 | GCM<AES>::Encryption encryption; |
34 | ||
35 | // Set key and IV | |
36 | 1 | encryption.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size()); |
37 | ||
38 | // Prepare output vector | |
39 | 1 | std::vector<unsigned char> ciphertext(plaintext.size()); |
40 | ||
41 | // Prepare authentication tag (16 bytes for GCM) | |
42 | 1 | auth_tag.resize(16); |
43 | ||
44 | // Perform encryption | |
45 | 1 | encryption.EncryptAndAuthenticate( |
46 | 1 | ciphertext.data(), |
47 | 1 | auth_tag.data(), |
48 | 1 | static_cast<int>(auth_tag.size()), |
49 | 1 | iv.data(), |
50 | 1 | static_cast<int>(iv.size()), |
51 | 1 | nullptr, |
52 | 1 | 0, // Additional authenticated data |
53 | 1 | plaintext.data(), |
54 | 1 | static_cast<int>(plaintext.size()) |
55 | 1 | ); |
56 | ||
57 | 1 | return ciphertext; |
58 | 1 | } |
59 | 1 | catch(const CryptoPP::Exception& e) { |
60 | 0 | throw std::runtime_error("Encryption failed: " + std::string(e.what())); |
61 | 0 | } |
62 | 1 | } |
63 | ||
64 | std::vector<unsigned char> Crypto::decrypt(const std::vector<unsigned char>& ciphertext, | |
65 | const std::vector<unsigned char>& key, | |
66 | const std::vector<unsigned char>& iv, | |
67 | 0 | const std::vector<unsigned char>& auth_tag) { |
68 | 0 | try { |
69 | // Create GCM mode decryption object | |
70 | 0 | GCM<AES>::Decryption decryption; |
71 | ||
72 | // Set key and IV | |
73 | 0 | decryption.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size()); |
74 | ||
75 | // Prepare output vector | |
76 | 0 | std::vector<unsigned char> plaintext(ciphertext.size()); |
77 | ||
78 | // Perform decryption and authentication | |
79 | 0 | bool valid = decryption.DecryptAndVerify( |
80 | 0 | plaintext.data(), |
81 | 0 | auth_tag.data(), |
82 | 0 | static_cast<int>(auth_tag.size()), |
83 | 0 | iv.data(), |
84 | 0 | static_cast<int>(iv.size()), |
85 | 0 | nullptr, |
86 | 0 | 0, // Additional authenticated data |
87 | 0 | ciphertext.data(), |
88 | 0 | static_cast<int>(ciphertext.size()) |
89 | 0 | ); |
90 | ||
91 | 0 | if(!valid) { |
92 | 0 | throw std::runtime_error("Authentication failed during decryption"); |
93 | 0 | } |
94 | ||
95 | 0 | return plaintext; |
96 | 0 | } |
97 | 0 | catch(const CryptoPP::Exception& e) { |
98 | 0 | throw std::runtime_error("Decryption failed: " + std::string(e.what())); |
99 | 0 | } |
100 | 0 | } |
101 | ||
102 | std::vector<unsigned char> Crypto::pbkdf2_hmac_sha256(const std::string& password, | |
103 | const std::vector<unsigned char>& salt, | |
104 | int iterations, | |
105 | 1 | size_t key_length) { |
106 | 1 | try { |
107 | 1 | std::vector<unsigned char> derived_key(key_length); |
108 | ||
109 | // Use Crypto++ PKCS5_PBKDF2_HMAC for key derivation | |
110 | 1 | PKCS5_PBKDF2_HMAC<SHA256> pbkdf; |
111 | 1 | pbkdf.DeriveKey( |
112 | 1 | derived_key.data(), |
113 | 1 | derived_key.size(), |
114 | 1 | 0x00, // Purpose byte |
115 | 1 | reinterpret_cast<const byte*>(password.data()), |
116 | 1 | password.length(), |
117 | 1 | salt.data(), |
118 | 1 | salt.size(), |
119 | 1 | iterations, |
120 | 1 | 0.0f // Timeout (0 = no timeout) |
121 | 1 | ); |
122 | ||
123 | 1 | return derived_key; |
124 | 1 | } |
125 | 1 | catch(const CryptoPP::Exception& e) { |
126 | 0 | throw std::runtime_error("PBKDF2 key derivation failed: " + std::string(e.what())); |
127 | 0 | } |
128 | 1 | } |
129 | ||
130 | 1 | std::vector<unsigned char> Crypto::compress(const std::vector<unsigned char>& data) { |
131 | 1 | try { |
132 | 1 | std::string compressed; |
133 | ||
134 | // Create zlib compressor | |
135 | 1 | ZlibCompressor compressor; |
136 | 1 | compressor.Put(data.data(), data.size()); |
137 | 1 | compressor.MessageEnd(); |
138 | ||
139 | // Retrieve compressed data | |
140 | 1 | size_t size = compressor.MaxRetrievable(); |
141 | 1 | compressed.resize(size); |
142 | 1 | compressor.Get(reinterpret_cast<byte*>(&compressed[0]), compressed.size()); |
143 | ||
144 | // Convert to vector | |
145 | 1 | return std::vector<unsigned char>(compressed.begin(), compressed.end()); |
146 | 1 | } |
147 | 1 | catch(const CryptoPP::Exception& e) { |
148 | 0 | throw std::runtime_error("Compression failed: " + std::string(e.what())); |
149 | 0 | } |
150 | 1 | } |
151 | ||
152 | 0 | std::vector<unsigned char> Crypto::decompress(const std::vector<unsigned char>& data) { |
153 | 0 | try { |
154 | 0 | std::string decompressed; |
155 | ||
156 | // Create zlib decompressor | |
157 | 0 | ZlibDecompressor decompressor; |
158 | 0 | decompressor.Put(data.data(), data.size()); |
159 | 0 | decompressor.MessageEnd(); |
160 | ||
161 | // Retrieve decompressed data | |
162 | 0 | size_t size = decompressor.MaxRetrievable(); |
163 | 0 | decompressed.resize(size); |
164 | 0 | decompressor.Get(reinterpret_cast<byte*>(&decompressed[0]), decompressed.size()); |
165 | ||
166 | // Convert to vector | |
167 | 0 | return std::vector<unsigned char>(decompressed.begin(), decompressed.end()); |
168 | 0 | } |
169 | 0 | catch(const CryptoPP::Exception& e) { |
170 | 0 | throw std::runtime_error("Decompression failed: " + std::string(e.what())); |
171 | 0 | } |
172 | 0 | } |
Line | Count | Source |
1 | #include "http_client.h" | |
2 | #include <iostream> | |
3 | #include <string> | |
4 | #include <sstream> | |
5 | ||
6 | #ifdef WINDOWS | |
7 | #include <windows.h> | |
8 | #include <winhttp.h> | |
9 | #pragma comment(lib, "winhttp.lib") | |
10 | 3 | static std::string last_winhttp_error(const char* where) { |
11 | 3 | DWORD err = GetLastError(); |
12 | 3 | LPVOID lpMsgBuf = nullptr; |
13 | 3 | FormatMessageA( |
14 | 3 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
15 | 3 | NULL, |
16 | 3 | err, |
17 | 3 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
18 | 3 | (LPSTR)&lpMsgBuf, |
19 | 3 | 0, NULL); |
20 | 3 | std::ostringstream os; |
21 | 3 | os << "[WinHTTP] " << where << " failed, error=" << err; |
22 | 3 | if (lpMsgBuf) { |
23 | 0 | os << ": " << (char*)lpMsgBuf; |
24 | 0 | LocalFree(lpMsgBuf); |
25 | 0 | } |
26 | 3 | return os.str(); |
27 | 3 | } |
28 | ||
29 | 4 | static std::wstring utf8_to_wide(const std::string& s) { |
30 | 4 | if (s.empty()) return std::wstring(); |
31 | 4 | int needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0); |
32 | 4 | std::wstring ws(needed, L'\0'); |
33 | 4 | MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &ws[0], needed); |
34 | 4 | return ws; |
35 | 4 | } |
36 | #elif LINUX | |
37 | #include <curl/curl.h> | |
38 | #endif | |
39 | ||
40 | #ifdef WINDOWS | |
41 | ||
42 | /* Entfernt: ungenutzte WinHttpData/Callback, um Clang-Warnungen zu vermeiden */ | |
43 | ||
44 | 2 | bool HttpClient::get(const std::string& url, std::string& response) { |
45 | // Parse URL | |
46 | 2 | URL_COMPONENTS urlComp; |
47 | 2 | ZeroMemory(&urlComp, sizeof(urlComp)); |
48 | 2 | urlComp.dwStructSize = sizeof(urlComp); |
49 | ||
50 | // Set required component lengths to non-zero to indicate they exist | |
51 | 2 | urlComp.dwHostNameLength = (DWORD)-1; |
52 | 2 | urlComp.dwUrlPathLength = (DWORD)-1; |
53 | 2 | urlComp.dwExtraInfoLength = (DWORD)-1; |
54 | ||
55 | // Parse the URL | |
56 | 2 | std::wstring wurl = utf8_to_wide(url); |
57 | 2 | if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) { |
58 | 0 | return false; |
59 | 0 | } |
60 | ||
61 | // Use WinHttpOpen to obtain a session handle | |
62 | 2 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
63 | 2 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
64 | 2 | WINHTTP_NO_PROXY_NAME, |
65 | 2 | WINHTTP_NO_PROXY_BYPASS, 0); |
66 | ||
67 | 2 | if (!hSession) { |
68 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(GET)") << std::endl; |
69 | 0 | return false; |
70 | 0 | } |
71 | // Force modern TLS versions to avoid handshake failures on some hosts | |
72 | 2 | DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
73 | 2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000 /* TLS1_3 if available */; |
74 | 2 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols)); |
75 | // Disable HTTP/2 if it causes issues | |
76 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
77 | DWORD features = WINHTTP_DISABLE_FEATURE_HTTP2; | |
78 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features, sizeof(features)); | |
79 | #endif | |
80 | ||
81 | // Specify an HTTP server (host must be null-terminated; urlComp provides length) | |
82 | 2 | std::wstring host = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
83 | 2 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
84 | 2 | : std::wstring(); |
85 | 2 | INTERNET_PORT port = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
86 | 2 | HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), port, 0); |
87 | ||
88 | 2 | if (!hConnect) { |
89 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(GET)") << std::endl; |
90 | 0 | WinHttpCloseHandle(hSession); |
91 | 0 | return false; |
92 | 0 | } |
93 | ||
94 | // Build object name = path + extra info (query) | |
95 | 2 | std::wstring pathPart = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) |
96 | 2 | ? std::wstring(urlComp.lpszUrlPath, urlComp.dwUrlPathLength) |
97 | 2 | : std::wstring(L"/"); |
98 | 2 | std::wstring extraPart = (urlComp.lpszExtraInfo && urlComp.dwExtraInfoLength > 0) |
99 | 2 | ? std::wstring(urlComp.lpszExtraInfo, urlComp.dwExtraInfoLength) |
100 | 2 | : std::wstring(); |
101 | 2 | std::wstring objectName = pathPart + extraPart; |
102 | ||
103 | // Create an HTTP request handle | |
104 | 2 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", |
105 | 2 | objectName.c_str(), |
106 | 2 | NULL, WINHTTP_NO_REFERER, |
107 | 2 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
108 | 2 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
109 | 2 | WINHTTP_FLAG_SECURE : 0); |
110 | // Set headers per API requirement | |
111 | 2 | LPCWSTR headers = L"X-Requested-With: JSONHttpRequest\r\nAccept: application/json"; |
112 | 2 | if (!WinHttpAddRequestHeaders(hRequest, headers, -1L, WINHTTP_ADDREQ_FLAG_ADD)) { |
113 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(GET)") << std::endl; |
114 | 0 | } |
115 | ||
116 | 2 | if (!hRequest) { |
117 | 0 | WinHttpCloseHandle(hConnect); |
118 | 0 | WinHttpCloseHandle(hSession); |
119 | 0 | return false; |
120 | 0 | } |
121 | ||
122 | // Send a request | |
123 | 2 | if (!WinHttpSendRequest(hRequest, |
124 | 2 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
125 | 2 | WINHTTP_NO_REQUEST_DATA, 0, |
126 | 2 | 0, 0)) { |
127 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(GET)") << std::endl; |
128 | 1 | WinHttpCloseHandle(hRequest); |
129 | 1 | WinHttpCloseHandle(hConnect); |
130 | 1 | WinHttpCloseHandle(hSession); |
131 | 1 | return false; |
132 | 1 | } |
133 | ||
134 | // End the request | |
135 | 1 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
136 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(GET)") << std::endl; |
137 | 0 | WinHttpCloseHandle(hRequest); |
138 | 0 | WinHttpCloseHandle(hConnect); |
139 | 0 | WinHttpCloseHandle(hSession); |
140 | 0 | return false; |
141 | 0 | } |
142 | ||
143 | // Keep checking for data until there is nothing left | |
144 | 1 | DWORD dwSize = 0; |
145 | 1 | DWORD dwDownloaded = 0; |
146 | 1 | std::string result; |
147 | ||
148 | 2 | do { |
149 | // Check for available data | |
150 | 2 | dwSize = 0; |
151 | 2 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
152 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(GET)") << std::endl; |
153 | 0 | WinHttpCloseHandle(hRequest); |
154 | 0 | WinHttpCloseHandle(hConnect); |
155 | 0 | WinHttpCloseHandle(hSession); |
156 | 0 | return false; |
157 | 0 | } |
158 | ||
159 | // Allocate space for the buffer | |
160 | 2 | char* pszOutBuffer = new char[dwSize + 1]; |
161 | 2 | if (!pszOutBuffer) { |
162 | 0 | WinHttpCloseHandle(hRequest); |
163 | 0 | WinHttpCloseHandle(hConnect); |
164 | 0 | WinHttpCloseHandle(hSession); |
165 | 0 | return false; |
166 | 0 | } |
167 | ||
168 | // Read the data | |
169 | 2 | ZeroMemory(pszOutBuffer, dwSize + 1); |
170 | 2 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
171 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(GET)") << std::endl; |
172 | 0 | delete[] pszOutBuffer; |
173 | 0 | WinHttpCloseHandle(hRequest); |
174 | 0 | WinHttpCloseHandle(hConnect); |
175 | 0 | WinHttpCloseHandle(hSession); |
176 | 0 | return false; |
177 | 0 | } |
178 | 2 | else { |
179 | 2 | result.append(pszOutBuffer); |
180 | 2 | } |
181 | ||
182 | // Free the memory | |
183 | 2 | delete[] pszOutBuffer; |
184 | ||
185 | 2 | } while (dwSize > 0); |
186 | ||
187 | 1 | response = result; |
188 | ||
189 | // Close any open handles | |
190 | 1 | WinHttpCloseHandle(hRequest); |
191 | 1 | WinHttpCloseHandle(hConnect); |
192 | 1 | WinHttpCloseHandle(hSession); |
193 | ||
194 | 1 | return true; |
195 | 1 | } |
196 | ||
197 | 1 | bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { |
198 | // Parse URL | |
199 | 1 | URL_COMPONENTS urlComp; |
200 | 1 | ZeroMemory(&urlComp, sizeof(urlComp)); |
201 | 1 | urlComp.dwStructSize = sizeof(urlComp); |
202 | ||
203 | // Set required component lengths to non-zero to indicate they exist | |
204 | 1 | urlComp.dwHostNameLength = (DWORD)-1; |
205 | 1 | urlComp.dwUrlPathLength = (DWORD)-1; |
206 | 1 | urlComp.dwExtraInfoLength = (DWORD)-1; |
207 | ||
208 | // Parse the URL | |
209 | 1 | std::wstring wurl = utf8_to_wide(url); |
210 | 1 | if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) { |
211 | 0 | return false; |
212 | 0 | } |
213 | ||
214 | // Use WinHttpOpen to obtain a session handle | |
215 | 1 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
216 | 1 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
217 | 1 | WINHTTP_NO_PROXY_NAME, |
218 | 1 | WINHTTP_NO_PROXY_BYPASS, 0); |
219 | ||
220 | 1 | if (!hSession) { |
221 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(POST)") << std::endl; |
222 | 0 | return false; |
223 | 0 | } |
224 | 1 | DWORD protocols2 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
225 | 1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000; |
226 | 1 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols2, sizeof(protocols2)); |
227 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
228 | DWORD features2 = WINHTTP_DISABLE_FEATURE_HTTP2; | |
229 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features2, sizeof(features2)); | |
230 | #endif | |
231 | ||
232 | // Specify an HTTP server (ensure host is null-terminated) | |
233 | 1 | std::wstring host2 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
234 | 1 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
235 | 1 | : std::wstring(); |
236 | 1 | INTERNET_PORT port2 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
237 | 1 | HINTERNET hConnect = WinHttpConnect(hSession, host2.c_str(), port2, 0); |
238 | ||
239 | 1 | if (!hConnect) { |
240 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(POST)") << std::endl; |
241 | 0 | WinHttpCloseHandle(hSession); |
242 | 0 | return false; |
243 | 0 | } |
244 | ||
245 | // Create an HTTP request handle (POST per API 1.3) | |
246 | 1 | LPCWSTR postPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/"; |
247 | 1 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", |
248 | 1 | postPath, |
249 | 1 | NULL, WINHTTP_NO_REFERER, |
250 | 1 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
251 | 1 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
252 | 1 | WINHTTP_FLAG_SECURE : 0); |
253 | ||
254 | 1 | if (!hRequest) { |
255 | 0 | std::cerr << "[WinHTTP] WinHttpOpenRequest(POST) failed" << std::endl; |
256 | 0 | WinHttpCloseHandle(hConnect); |
257 | 0 | WinHttpCloseHandle(hSession); |
258 | 0 | return false; |
259 | 0 | } |
260 | ||
261 | // Set headers | |
262 | 1 | LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; |
263 | 1 | BOOL bResults = WinHttpAddRequestHeaders(hRequest, |
264 | 1 | headers, |
265 | 1 | -1L, |
266 | 1 | WINHTTP_ADDREQ_FLAG_ADD); |
267 | ||
268 | 1 | if (!bResults) { |
269 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(POST)") << std::endl; |
270 | 0 | WinHttpCloseHandle(hRequest); |
271 | 0 | WinHttpCloseHandle(hConnect); |
272 | 0 | WinHttpCloseHandle(hSession); |
273 | 0 | return false; |
274 | 0 | } |
275 | ||
276 | // Send a request | |
277 | // Send UTF-8 bytes as body (raw) | |
278 | 1 | if (!WinHttpSendRequest(hRequest, |
279 | 1 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
280 | 1 | (LPVOID)data.c_str(), (DWORD)data.length(), |
281 | 1 | (DWORD)data.length(), 0)) { |
282 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(POST)") << std::endl; |
283 | 1 | WinHttpCloseHandle(hRequest); |
284 | 1 | WinHttpCloseHandle(hConnect); |
285 | 1 | WinHttpCloseHandle(hSession); |
286 | 1 | return false; |
287 | 1 | } |
288 | ||
289 | // End the request | |
290 | 0 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
291 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(POST)") << std::endl; |
292 | 0 | WinHttpCloseHandle(hRequest); |
293 | 0 | WinHttpCloseHandle(hConnect); |
294 | 0 | WinHttpCloseHandle(hSession); |
295 | 0 | return false; |
296 | 0 | } |
297 | ||
298 | // Keep checking for data until there is nothing left | |
299 | 0 | DWORD dwSize = 0; |
300 | 0 | DWORD dwDownloaded = 0; |
301 | 0 | std::string result; |
302 | ||
303 | 0 | do { |
304 | // Check for available data | |
305 | 0 | dwSize = 0; |
306 | 0 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
307 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(POST)") << std::endl; |
308 | 0 | WinHttpCloseHandle(hRequest); |
309 | 0 | WinHttpCloseHandle(hConnect); |
310 | 0 | WinHttpCloseHandle(hSession); |
311 | 0 | return false; |
312 | 0 | } |
313 | ||
314 | // Allocate space for the buffer | |
315 | 0 | char* pszOutBuffer = new char[dwSize + 1]; |
316 | 0 | if (!pszOutBuffer) { |
317 | 0 | WinHttpCloseHandle(hRequest); |
318 | 0 | WinHttpCloseHandle(hConnect); |
319 | 0 | WinHttpCloseHandle(hSession); |
320 | 0 | return false; |
321 | 0 | } |
322 | ||
323 | // Read the data | |
324 | 0 | ZeroMemory(pszOutBuffer, dwSize + 1); |
325 | 0 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
326 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(POST)") << std::endl; |
327 | 0 | delete[] pszOutBuffer; |
328 | 0 | WinHttpCloseHandle(hRequest); |
329 | 0 | WinHttpCloseHandle(hConnect); |
330 | 0 | WinHttpCloseHandle(hSession); |
331 | 0 | return false; |
332 | 0 | } |
333 | 0 | else { |
334 | 0 | result.append(pszOutBuffer); |
335 | 0 | } |
336 | ||
337 | // Free the memory | |
338 | 0 | delete[] pszOutBuffer; |
339 | ||
340 | 0 | } while (dwSize > 0); |
341 | ||
342 | 0 | response = result; |
343 | ||
344 | // Close any open handles | |
345 | 0 | WinHttpCloseHandle(hRequest); |
346 | 0 | WinHttpCloseHandle(hConnect); |
347 | 0 | WinHttpCloseHandle(hSession); |
348 | ||
349 | 0 | return true; |
350 | 0 | } |
351 | ||
352 | 1 | bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { |
353 | // Parse URL | |
354 | 1 | URL_COMPONENTS urlComp; |
355 | 1 | ZeroMemory(&urlComp, sizeof(urlComp)); |
356 | 1 | urlComp.dwStructSize = sizeof(urlComp); |
357 | ||
358 | // Set required component lengths to non-zero to indicate they exist | |
359 | 1 | urlComp.dwHostNameLength = (DWORD)-1; |
360 | 1 | urlComp.dwUrlPathLength = (DWORD)-1; |
361 | 1 | urlComp.dwExtraInfoLength = (DWORD)-1; |
362 | ||
363 | // Parse the URL | |
364 | 1 | std::wstring wurlDel = utf8_to_wide(url); |
365 | 1 | if (!WinHttpCrackUrl(wurlDel.c_str(), 0, 0, &urlComp)) { |
366 | 0 | return false; |
367 | 0 | } |
368 | ||
369 | // Use WinHttpOpen to obtain a session handle | |
370 | 1 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
371 | 1 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
372 | 1 | WINHTTP_NO_PROXY_NAME, |
373 | 1 | WINHTTP_NO_PROXY_BYPASS, 0); |
374 | ||
375 | 1 | if (!hSession) { |
376 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(DEL)") << std::endl; |
377 | 0 | return false; |
378 | 0 | } |
379 | 1 | DWORD protocols3 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
380 | 1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000; |
381 | 1 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols3, sizeof(protocols3)); |
382 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
383 | DWORD features3 = WINHTTP_DISABLE_FEATURE_HTTP2; | |
384 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features3, sizeof(features3)); | |
385 | #endif | |
386 | ||
387 | // Specify an HTTP server (ensure host is null-terminated) | |
388 | 1 | std::wstring host3 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
389 | 1 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
390 | 1 | : std::wstring(); |
391 | 1 | INTERNET_PORT port3 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
392 | 1 | HINTERNET hConnect = WinHttpConnect(hSession, host3.c_str(), port3, 0); |
393 | ||
394 | 1 | if (!hConnect) { |
395 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(DEL)") << std::endl; |
396 | 0 | WinHttpCloseHandle(hSession); |
397 | 0 | return false; |
398 | 0 | } |
399 | ||
400 | // Create an HTTP request handle | |
401 | 1 | LPCWSTR delPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/"; |
402 | 1 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", |
403 | 1 | delPath, |
404 | 1 | NULL, WINHTTP_NO_REFERER, |
405 | 1 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
406 | 1 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
407 | 1 | WINHTTP_FLAG_SECURE : 0); |
408 | ||
409 | 1 | if (!hRequest) { |
410 | 0 | std::cerr << "[WinHTTP] WinHttpOpenRequest(DEL) failed" << std::endl; |
411 | 0 | WinHttpCloseHandle(hConnect); |
412 | 0 | WinHttpCloseHandle(hSession); |
413 | 0 | return false; |
414 | 0 | } |
415 | ||
416 | // Set headers | |
417 | 1 | LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; |
418 | 1 | BOOL bResults = WinHttpAddRequestHeaders(hRequest, |
419 | 1 | headers, |
420 | 1 | -1L, |
421 | 1 | WINHTTP_ADDREQ_FLAG_ADD); |
422 | ||
423 | 1 | if (!bResults) { |
424 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(DEL)") << std::endl; |
425 | 0 | WinHttpCloseHandle(hRequest); |
426 | 0 | WinHttpCloseHandle(hConnect); |
427 | 0 | WinHttpCloseHandle(hSession); |
428 | 0 | return false; |
429 | 0 | } |
430 | ||
431 | // Send a request body with DELETE method | |
432 | 1 | if (!WinHttpSendRequest(hRequest, |
433 | 1 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
434 | 1 | (LPVOID)data.c_str(), (DWORD)data.length(), |
435 | 1 | (DWORD)data.length(), 0)) { |
436 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(DEL)") << std::endl; |
437 | 1 | WinHttpCloseHandle(hRequest); |
438 | 1 | WinHttpCloseHandle(hConnect); |
439 | 1 | WinHttpCloseHandle(hSession); |
440 | 1 | return false; |
441 | 1 | } |
442 | ||
443 | // End the request | |
444 | 0 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
445 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(DEL)") << std::endl; |
446 | 0 | WinHttpCloseHandle(hRequest); |
447 | 0 | WinHttpCloseHandle(hConnect); |
448 | 0 | WinHttpCloseHandle(hSession); |
449 | 0 | return false; |
450 | 0 | } |
451 | ||
452 | // Keep checking for data until there is nothing left | |
453 | 0 | DWORD dwSize = 0; |
454 | 0 | DWORD dwDownloaded = 0; |
455 | 0 | std::string result; |
456 | ||
457 | 0 | do { |
458 | // Check for available data | |
459 | 0 | dwSize = 0; |
460 | 0 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
461 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(DEL)") << std::endl; |
462 | 0 | WinHttpCloseHandle(hRequest); |
463 | 0 | WinHttpCloseHandle(hConnect); |
464 | 0 | WinHttpCloseHandle(hSession); |
465 | 0 | return false; |
466 | 0 | } |
467 | ||
468 | // Allocate space for the buffer | |
469 | 0 | char* pszOutBuffer = new char[dwSize + 1]; |
470 | 0 | if (!pszOutBuffer) { |
471 | 0 | WinHttpCloseHandle(hRequest); |
472 | 0 | WinHttpCloseHandle(hConnect); |
473 | 0 | WinHttpCloseHandle(hSession); |
474 | 0 | return false; |
475 | 0 | } |
476 | ||
477 | // Read the data | |
478 | 0 | ZeroMemory(pszOutBuffer, dwSize + 1); |
479 | 0 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
480 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(DEL)") << std::endl; |
481 | 0 | delete[] pszOutBuffer; |
482 | 0 | WinHttpCloseHandle(hRequest); |
483 | 0 | WinHttpCloseHandle(hConnect); |
484 | 0 | WinHttpCloseHandle(hSession); |
485 | 0 | return false; |
486 | 0 | } |
487 | 0 | else { |
488 | 0 | result.append(pszOutBuffer); |
489 | 0 | } |
490 | ||
491 | // Free the memory | |
492 | 0 | delete[] pszOutBuffer; |
493 | ||
494 | 0 | } while (dwSize > 0); |
495 | ||
496 | 0 | response = result; |
497 | ||
498 | // Close any open handles | |
499 | 0 | WinHttpCloseHandle(hRequest); |
500 | 0 | WinHttpCloseHandle(hConnect); |
501 | 0 | WinHttpCloseHandle(hSession); |
502 | ||
503 | 0 | return true; |
504 | 0 | } |
505 | ||
506 | #elif LINUX | |
507 | ||
508 | static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { | |
509 | size_t realsize = size * nmemb; | |
510 | userp->append((char*)contents, realsize); | |
511 | return realsize; | |
512 | } | |
513 | ||
514 | bool HttpClient::get(const std::string& url, std::string& response) { | |
515 | CURL* curl; | |
516 | CURLcode res; | |
517 | ||
518 | curl = curl_easy_init(); | |
519 | if (!curl) { | |
520 | return false; | |
521 | } | |
522 | ||
523 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
524 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
525 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
526 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
527 | ||
528 | struct curl_slist* headers = NULL; | |
529 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
530 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
531 | ||
532 | res = curl_easy_perform(curl); | |
533 | ||
534 | curl_slist_free_all(headers); | |
535 | curl_easy_cleanup(curl); | |
536 | ||
537 | return (res == CURLE_OK); | |
538 | } | |
539 | ||
540 | bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { | |
541 | CURL* curl; | |
542 | CURLcode res; | |
543 | ||
544 | curl = curl_easy_init(); | |
545 | if (!curl) { | |
546 | return false; | |
547 | } | |
548 | ||
549 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
550 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
551 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
552 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
553 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
554 | ||
555 | struct curl_slist* headers = NULL; | |
556 | headers = curl_slist_append(headers, "Content-Type: application/json"); | |
557 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
558 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
559 | ||
560 | res = curl_easy_perform(curl); | |
561 | ||
562 | curl_slist_free_all(headers); | |
563 | curl_easy_cleanup(curl); | |
564 | ||
565 | return (res == CURLE_OK); | |
566 | } | |
567 | ||
568 | bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { | |
569 | CURL* curl; | |
570 | CURLcode res; | |
571 | ||
572 | curl = curl_easy_init(); | |
573 | if (!curl) { | |
574 | return false; | |
575 | } | |
576 | ||
577 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
578 | // PrivateBin API erwartet POST für delete | |
579 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
580 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
581 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
582 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
583 | ||
584 | struct curl_slist* headers = NULL; | |
585 | headers = curl_slist_append(headers, "Content-Type: application/json"); | |
586 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
587 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
588 | ||
589 | res = curl_easy_perform(curl); | |
590 | ||
591 | curl_slist_free_all(headers); | |
592 | curl_easy_cleanup(curl); | |
593 | ||
594 | return (res == CURLE_OK); | |
595 | } | |
596 | ||
597 | #endif |
Line | Count | Source |
1 | #include "http_client.h" | |
2 | #include <iostream> | |
3 | #include <string> | |
4 | #include <sstream> | |
5 | ||
6 | #ifdef WINDOWS | |
7 | #include <windows.h> | |
8 | #include <winhttp.h> | |
9 | #pragma comment(lib, "winhttp.lib") | |
10 | 3 | static std::string last_winhttp_error(const char* where) { |
11 | 3 | DWORD err = GetLastError(); |
12 | 3 | LPVOID lpMsgBuf = nullptr; |
13 | 3 | FormatMessageA( |
14 | 3 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
15 | 3 | NULL, |
16 | 3 | err, |
17 | 3 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
18 | 3 | (LPSTR)&lpMsgBuf, |
19 | 3 | 0, NULL); |
20 | 3 | std::ostringstream os; |
21 | 3 | os << "[WinHTTP] " << where << " failed, error=" << err; |
22 | 3 | if (lpMsgBuf) { |
23 | 0 | os << ": " << (char*)lpMsgBuf; |
24 | 0 | LocalFree(lpMsgBuf); |
25 | 0 | } |
26 | 3 | return os.str(); |
27 | 3 | } |
28 | ||
29 | 4 | static std::wstring utf8_to_wide(const std::string& s) { |
30 | 4 | if (s.empty()) return std::wstring(); |
31 | 4 | int needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0); |
32 | 4 | std::wstring ws(needed, L'\0'); |
33 | 4 | MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &ws[0], needed); |
34 | 4 | return ws; |
35 | 4 | } |
36 | #elif LINUX | |
37 | #include <curl/curl.h> | |
38 | #endif | |
39 | ||
40 | #ifdef WINDOWS | |
41 | ||
42 | /* Entfernt: ungenutzte WinHttpData/Callback, um Clang-Warnungen zu vermeiden */ | |
43 | ||
44 | 2 | bool HttpClient::get(const std::string& url, std::string& response) { |
45 | // Parse URL | |
46 | 2 | URL_COMPONENTS urlComp; |
47 | 2 | ZeroMemory(&urlComp, sizeof(urlComp)); |
48 | 2 | urlComp.dwStructSize = sizeof(urlComp); |
49 | ||
50 | // Set required component lengths to non-zero to indicate they exist | |
51 | 2 | urlComp.dwHostNameLength = (DWORD)-1; |
52 | 2 | urlComp.dwUrlPathLength = (DWORD)-1; |
53 | 2 | urlComp.dwExtraInfoLength = (DWORD)-1; |
54 | ||
55 | // Parse the URL | |
56 | 2 | std::wstring wurl = utf8_to_wide(url); |
57 | 2 | if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) { |
58 | 0 | return false; |
59 | 0 | } |
60 | ||
61 | // Use WinHttpOpen to obtain a session handle | |
62 | 2 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
63 | 2 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
64 | 2 | WINHTTP_NO_PROXY_NAME, |
65 | 2 | WINHTTP_NO_PROXY_BYPASS, 0); |
66 | ||
67 | 2 | if (!hSession) { |
68 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(GET)") << std::endl; |
69 | 0 | return false; |
70 | 0 | } |
71 | // Force modern TLS versions to avoid handshake failures on some hosts | |
72 | 2 | DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
73 | 2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000 /* TLS1_3 if available */; |
74 | 2 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols, sizeof(protocols)); |
75 | // Disable HTTP/2 if it causes issues | |
76 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
77 | DWORD features = WINHTTP_DISABLE_FEATURE_HTTP2; | |
78 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features, sizeof(features)); | |
79 | #endif | |
80 | ||
81 | // Specify an HTTP server (host must be null-terminated; urlComp provides length) | |
82 | 2 | std::wstring host = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
83 | 2 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
84 | 2 | : std::wstring(); |
85 | 2 | INTERNET_PORT port = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
86 | 2 | HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), port, 0); |
87 | ||
88 | 2 | if (!hConnect) { |
89 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(GET)") << std::endl; |
90 | 0 | WinHttpCloseHandle(hSession); |
91 | 0 | return false; |
92 | 0 | } |
93 | ||
94 | // Build object name = path + extra info (query) | |
95 | 2 | std::wstring pathPart = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) |
96 | 2 | ? std::wstring(urlComp.lpszUrlPath, urlComp.dwUrlPathLength) |
97 | 2 | : std::wstring(L"/"); |
98 | 2 | std::wstring extraPart = (urlComp.lpszExtraInfo && urlComp.dwExtraInfoLength > 0) |
99 | 2 | ? std::wstring(urlComp.lpszExtraInfo, urlComp.dwExtraInfoLength) |
100 | 2 | : std::wstring(); |
101 | 2 | std::wstring objectName = pathPart + extraPart; |
102 | ||
103 | // Create an HTTP request handle | |
104 | 2 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", |
105 | 2 | objectName.c_str(), |
106 | 2 | NULL, WINHTTP_NO_REFERER, |
107 | 2 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
108 | 2 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
109 | 2 | WINHTTP_FLAG_SECURE : 0); |
110 | // Set headers per API requirement | |
111 | 2 | LPCWSTR headers = L"X-Requested-With: JSONHttpRequest\r\nAccept: application/json"; |
112 | 2 | if (!WinHttpAddRequestHeaders(hRequest, headers, -1L, WINHTTP_ADDREQ_FLAG_ADD)) { |
113 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(GET)") << std::endl; |
114 | 0 | } |
115 | ||
116 | 2 | if (!hRequest) { |
117 | 0 | WinHttpCloseHandle(hConnect); |
118 | 0 | WinHttpCloseHandle(hSession); |
119 | 0 | return false; |
120 | 0 | } |
121 | ||
122 | // Send a request | |
123 | 2 | if (!WinHttpSendRequest(hRequest, |
124 | 2 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
125 | 2 | WINHTTP_NO_REQUEST_DATA, 0, |
126 | 2 | 0, 0)) { |
127 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(GET)") << std::endl; |
128 | 1 | WinHttpCloseHandle(hRequest); |
129 | 1 | WinHttpCloseHandle(hConnect); |
130 | 1 | WinHttpCloseHandle(hSession); |
131 | 1 | return false; |
132 | 1 | } |
133 | ||
134 | // End the request | |
135 | 1 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
136 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(GET)") << std::endl; |
137 | 0 | WinHttpCloseHandle(hRequest); |
138 | 0 | WinHttpCloseHandle(hConnect); |
139 | 0 | WinHttpCloseHandle(hSession); |
140 | 0 | return false; |
141 | 0 | } |
142 | ||
143 | // Keep checking for data until there is nothing left | |
144 | 1 | DWORD dwSize = 0; |
145 | 1 | DWORD dwDownloaded = 0; |
146 | 1 | std::string result; |
147 | ||
148 | 2 | do { |
149 | // Check for available data | |
150 | 2 | dwSize = 0; |
151 | 2 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
152 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(GET)") << std::endl; |
153 | 0 | WinHttpCloseHandle(hRequest); |
154 | 0 | WinHttpCloseHandle(hConnect); |
155 | 0 | WinHttpCloseHandle(hSession); |
156 | 0 | return false; |
157 | 0 | } |
158 | ||
159 | // Allocate space for the buffer | |
160 | 2 | char* pszOutBuffer = new char[dwSize + 1]; |
161 | 2 | if (!pszOutBuffer) { |
162 | 0 | WinHttpCloseHandle(hRequest); |
163 | 0 | WinHttpCloseHandle(hConnect); |
164 | 0 | WinHttpCloseHandle(hSession); |
165 | 0 | return false; |
166 | 0 | } |
167 | ||
168 | // Read the data | |
169 | 2 | ZeroMemory(pszOutBuffer, dwSize + 1); |
170 | 2 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
171 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(GET)") << std::endl; |
172 | 0 | delete[] pszOutBuffer; |
173 | 0 | WinHttpCloseHandle(hRequest); |
174 | 0 | WinHttpCloseHandle(hConnect); |
175 | 0 | WinHttpCloseHandle(hSession); |
176 | 0 | return false; |
177 | 0 | } |
178 | 2 | else { |
179 | 2 | result.append(pszOutBuffer); |
180 | 2 | } |
181 | ||
182 | // Free the memory | |
183 | 2 | delete[] pszOutBuffer; |
184 | ||
185 | 2 | } while (dwSize > 0); |
186 | ||
187 | 1 | response = result; |
188 | ||
189 | // Close any open handles | |
190 | 1 | WinHttpCloseHandle(hRequest); |
191 | 1 | WinHttpCloseHandle(hConnect); |
192 | 1 | WinHttpCloseHandle(hSession); |
193 | ||
194 | 1 | return true; |
195 | 1 | } |
196 | ||
197 | 1 | bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { |
198 | // Parse URL | |
199 | 1 | URL_COMPONENTS urlComp; |
200 | 1 | ZeroMemory(&urlComp, sizeof(urlComp)); |
201 | 1 | urlComp.dwStructSize = sizeof(urlComp); |
202 | ||
203 | // Set required component lengths to non-zero to indicate they exist | |
204 | 1 | urlComp.dwHostNameLength = (DWORD)-1; |
205 | 1 | urlComp.dwUrlPathLength = (DWORD)-1; |
206 | 1 | urlComp.dwExtraInfoLength = (DWORD)-1; |
207 | ||
208 | // Parse the URL | |
209 | 1 | std::wstring wurl = utf8_to_wide(url); |
210 | 1 | if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) { |
211 | 0 | return false; |
212 | 0 | } |
213 | ||
214 | // Use WinHttpOpen to obtain a session handle | |
215 | 1 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
216 | 1 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
217 | 1 | WINHTTP_NO_PROXY_NAME, |
218 | 1 | WINHTTP_NO_PROXY_BYPASS, 0); |
219 | ||
220 | 1 | if (!hSession) { |
221 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(POST)") << std::endl; |
222 | 0 | return false; |
223 | 0 | } |
224 | 1 | DWORD protocols2 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
225 | 1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000; |
226 | 1 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols2, sizeof(protocols2)); |
227 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
228 | DWORD features2 = WINHTTP_DISABLE_FEATURE_HTTP2; | |
229 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features2, sizeof(features2)); | |
230 | #endif | |
231 | ||
232 | // Specify an HTTP server (ensure host is null-terminated) | |
233 | 1 | std::wstring host2 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
234 | 1 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
235 | 1 | : std::wstring(); |
236 | 1 | INTERNET_PORT port2 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
237 | 1 | HINTERNET hConnect = WinHttpConnect(hSession, host2.c_str(), port2, 0); |
238 | ||
239 | 1 | if (!hConnect) { |
240 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(POST)") << std::endl; |
241 | 0 | WinHttpCloseHandle(hSession); |
242 | 0 | return false; |
243 | 0 | } |
244 | ||
245 | // Create an HTTP request handle (POST per API 1.3) | |
246 | 1 | LPCWSTR postPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/"; |
247 | 1 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", |
248 | 1 | postPath, |
249 | 1 | NULL, WINHTTP_NO_REFERER, |
250 | 1 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
251 | 1 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
252 | 1 | WINHTTP_FLAG_SECURE : 0); |
253 | ||
254 | 1 | if (!hRequest) { |
255 | 0 | std::cerr << "[WinHTTP] WinHttpOpenRequest(POST) failed" << std::endl; |
256 | 0 | WinHttpCloseHandle(hConnect); |
257 | 0 | WinHttpCloseHandle(hSession); |
258 | 0 | return false; |
259 | 0 | } |
260 | ||
261 | // Set headers | |
262 | 1 | LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; |
263 | 1 | BOOL bResults = WinHttpAddRequestHeaders(hRequest, |
264 | 1 | headers, |
265 | 1 | -1L, |
266 | 1 | WINHTTP_ADDREQ_FLAG_ADD); |
267 | ||
268 | 1 | if (!bResults) { |
269 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(POST)") << std::endl; |
270 | 0 | WinHttpCloseHandle(hRequest); |
271 | 0 | WinHttpCloseHandle(hConnect); |
272 | 0 | WinHttpCloseHandle(hSession); |
273 | 0 | return false; |
274 | 0 | } |
275 | ||
276 | // Send a request | |
277 | // Send UTF-8 bytes as body (raw) | |
278 | 1 | if (!WinHttpSendRequest(hRequest, |
279 | 1 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
280 | 1 | (LPVOID)data.c_str(), (DWORD)data.length(), |
281 | 1 | (DWORD)data.length(), 0)) { |
282 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(POST)") << std::endl; |
283 | 1 | WinHttpCloseHandle(hRequest); |
284 | 1 | WinHttpCloseHandle(hConnect); |
285 | 1 | WinHttpCloseHandle(hSession); |
286 | 1 | return false; |
287 | 1 | } |
288 | ||
289 | // End the request | |
290 | 0 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
291 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(POST)") << std::endl; |
292 | 0 | WinHttpCloseHandle(hRequest); |
293 | 0 | WinHttpCloseHandle(hConnect); |
294 | 0 | WinHttpCloseHandle(hSession); |
295 | 0 | return false; |
296 | 0 | } |
297 | ||
298 | // Keep checking for data until there is nothing left | |
299 | 0 | DWORD dwSize = 0; |
300 | 0 | DWORD dwDownloaded = 0; |
301 | 0 | std::string result; |
302 | ||
303 | 0 | do { |
304 | // Check for available data | |
305 | 0 | dwSize = 0; |
306 | 0 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
307 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(POST)") << std::endl; |
308 | 0 | WinHttpCloseHandle(hRequest); |
309 | 0 | WinHttpCloseHandle(hConnect); |
310 | 0 | WinHttpCloseHandle(hSession); |
311 | 0 | return false; |
312 | 0 | } |
313 | ||
314 | // Allocate space for the buffer | |
315 | 0 | char* pszOutBuffer = new char[dwSize + 1]; |
316 | 0 | if (!pszOutBuffer) { |
317 | 0 | WinHttpCloseHandle(hRequest); |
318 | 0 | WinHttpCloseHandle(hConnect); |
319 | 0 | WinHttpCloseHandle(hSession); |
320 | 0 | return false; |
321 | 0 | } |
322 | ||
323 | // Read the data | |
324 | 0 | ZeroMemory(pszOutBuffer, dwSize + 1); |
325 | 0 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
326 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(POST)") << std::endl; |
327 | 0 | delete[] pszOutBuffer; |
328 | 0 | WinHttpCloseHandle(hRequest); |
329 | 0 | WinHttpCloseHandle(hConnect); |
330 | 0 | WinHttpCloseHandle(hSession); |
331 | 0 | return false; |
332 | 0 | } |
333 | 0 | else { |
334 | 0 | result.append(pszOutBuffer); |
335 | 0 | } |
336 | ||
337 | // Free the memory | |
338 | 0 | delete[] pszOutBuffer; |
339 | ||
340 | 0 | } while (dwSize > 0); |
341 | ||
342 | 0 | response = result; |
343 | ||
344 | // Close any open handles | |
345 | 0 | WinHttpCloseHandle(hRequest); |
346 | 0 | WinHttpCloseHandle(hConnect); |
347 | 0 | WinHttpCloseHandle(hSession); |
348 | ||
349 | 0 | return true; |
350 | 0 | } |
351 | ||
352 | 1 | bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { |
353 | // Parse URL | |
354 | 1 | URL_COMPONENTS urlComp; |
355 | 1 | ZeroMemory(&urlComp, sizeof(urlComp)); |
356 | 1 | urlComp.dwStructSize = sizeof(urlComp); |
357 | ||
358 | // Set required component lengths to non-zero to indicate they exist | |
359 | 1 | urlComp.dwHostNameLength = (DWORD)-1; |
360 | 1 | urlComp.dwUrlPathLength = (DWORD)-1; |
361 | 1 | urlComp.dwExtraInfoLength = (DWORD)-1; |
362 | ||
363 | // Parse the URL | |
364 | 1 | std::wstring wurlDel = utf8_to_wide(url); |
365 | 1 | if (!WinHttpCrackUrl(wurlDel.c_str(), 0, 0, &urlComp)) { |
366 | 0 | return false; |
367 | 0 | } |
368 | ||
369 | // Use WinHttpOpen to obtain a session handle | |
370 | 1 | HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", |
371 | 1 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, |
372 | 1 | WINHTTP_NO_PROXY_NAME, |
373 | 1 | WINHTTP_NO_PROXY_BYPASS, 0); |
374 | ||
375 | 1 | if (!hSession) { |
376 | 0 | std::cerr << last_winhttp_error("WinHttpOpen(DEL)") << std::endl; |
377 | 0 | return false; |
378 | 0 | } |
379 | 1 | DWORD protocols3 = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | |
380 | 1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | 0x00002000; |
381 | 1 | WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &protocols3, sizeof(protocols3)); |
382 | #ifdef WINHTTP_DISABLE_FEATURE_HTTP2 | |
383 | DWORD features3 = WINHTTP_DISABLE_FEATURE_HTTP2; | |
384 | WinHttpSetOption(hSession, WINHTTP_OPTION_DISABLE_FEATURE, &features3, sizeof(features3)); | |
385 | #endif | |
386 | ||
387 | // Specify an HTTP server (ensure host is null-terminated) | |
388 | 1 | std::wstring host3 = (urlComp.lpszHostName && urlComp.dwHostNameLength > 0) |
389 | 1 | ? std::wstring(urlComp.lpszHostName, urlComp.dwHostNameLength) |
390 | 1 | : std::wstring(); |
391 | 1 | INTERNET_PORT port3 = urlComp.nPort ? urlComp.nPort : ((urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT); |
392 | 1 | HINTERNET hConnect = WinHttpConnect(hSession, host3.c_str(), port3, 0); |
393 | ||
394 | 1 | if (!hConnect) { |
395 | 0 | std::cerr << last_winhttp_error("WinHttpConnect(DEL)") << std::endl; |
396 | 0 | WinHttpCloseHandle(hSession); |
397 | 0 | return false; |
398 | 0 | } |
399 | ||
400 | // Create an HTTP request handle | |
401 | 1 | LPCWSTR delPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/"; |
402 | 1 | HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", |
403 | 1 | delPath, |
404 | 1 | NULL, WINHTTP_NO_REFERER, |
405 | 1 | WINHTTP_DEFAULT_ACCEPT_TYPES, |
406 | 1 | (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? |
407 | 1 | WINHTTP_FLAG_SECURE : 0); |
408 | ||
409 | 1 | if (!hRequest) { |
410 | 0 | std::cerr << "[WinHTTP] WinHttpOpenRequest(DEL) failed" << std::endl; |
411 | 0 | WinHttpCloseHandle(hConnect); |
412 | 0 | WinHttpCloseHandle(hSession); |
413 | 0 | return false; |
414 | 0 | } |
415 | ||
416 | // Set headers | |
417 | 1 | LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; |
418 | 1 | BOOL bResults = WinHttpAddRequestHeaders(hRequest, |
419 | 1 | headers, |
420 | 1 | -1L, |
421 | 1 | WINHTTP_ADDREQ_FLAG_ADD); |
422 | ||
423 | 1 | if (!bResults) { |
424 | 0 | std::cerr << last_winhttp_error("WinHttpAddRequestHeaders(DEL)") << std::endl; |
425 | 0 | WinHttpCloseHandle(hRequest); |
426 | 0 | WinHttpCloseHandle(hConnect); |
427 | 0 | WinHttpCloseHandle(hSession); |
428 | 0 | return false; |
429 | 0 | } |
430 | ||
431 | // Send a request body with DELETE method | |
432 | 1 | if (!WinHttpSendRequest(hRequest, |
433 | 1 | WINHTTP_NO_ADDITIONAL_HEADERS, 0, |
434 | 1 | (LPVOID)data.c_str(), (DWORD)data.length(), |
435 | 1 | (DWORD)data.length(), 0)) { |
436 | 1 | std::cerr << last_winhttp_error("WinHttpSendRequest(DEL)") << std::endl; |
437 | 1 | WinHttpCloseHandle(hRequest); |
438 | 1 | WinHttpCloseHandle(hConnect); |
439 | 1 | WinHttpCloseHandle(hSession); |
440 | 1 | return false; |
441 | 1 | } |
442 | ||
443 | // End the request | |
444 | 0 | if (!WinHttpReceiveResponse(hRequest, NULL)) { |
445 | 0 | std::cerr << last_winhttp_error("WinHttpReceiveResponse(DEL)") << std::endl; |
446 | 0 | WinHttpCloseHandle(hRequest); |
447 | 0 | WinHttpCloseHandle(hConnect); |
448 | 0 | WinHttpCloseHandle(hSession); |
449 | 0 | return false; |
450 | 0 | } |
451 | ||
452 | // Keep checking for data until there is nothing left | |
453 | 0 | DWORD dwSize = 0; |
454 | 0 | DWORD dwDownloaded = 0; |
455 | 0 | std::string result; |
456 | ||
457 | 0 | do { |
458 | // Check for available data | |
459 | 0 | dwSize = 0; |
460 | 0 | if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
461 | 0 | std::cerr << last_winhttp_error("WinHttpQueryDataAvailable(DEL)") << std::endl; |
462 | 0 | WinHttpCloseHandle(hRequest); |
463 | 0 | WinHttpCloseHandle(hConnect); |
464 | 0 | WinHttpCloseHandle(hSession); |
465 | 0 | return false; |
466 | 0 | } |
467 | ||
468 | // Allocate space for the buffer | |
469 | 0 | char* pszOutBuffer = new char[dwSize + 1]; |
470 | 0 | if (!pszOutBuffer) { |
471 | 0 | WinHttpCloseHandle(hRequest); |
472 | 0 | WinHttpCloseHandle(hConnect); |
473 | 0 | WinHttpCloseHandle(hSession); |
474 | 0 | return false; |
475 | 0 | } |
476 | ||
477 | // Read the data | |
478 | 0 | ZeroMemory(pszOutBuffer, dwSize + 1); |
479 | 0 | if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { |
480 | 0 | std::cerr << last_winhttp_error("WinHttpReadData(DEL)") << std::endl; |
481 | 0 | delete[] pszOutBuffer; |
482 | 0 | WinHttpCloseHandle(hRequest); |
483 | 0 | WinHttpCloseHandle(hConnect); |
484 | 0 | WinHttpCloseHandle(hSession); |
485 | 0 | return false; |
486 | 0 | } |
487 | 0 | else { |
488 | 0 | result.append(pszOutBuffer); |
489 | 0 | } |
490 | ||
491 | // Free the memory | |
492 | 0 | delete[] pszOutBuffer; |
493 | ||
494 | 0 | } while (dwSize > 0); |
495 | ||
496 | 0 | response = result; |
497 | ||
498 | // Close any open handles | |
499 | 0 | WinHttpCloseHandle(hRequest); |
500 | 0 | WinHttpCloseHandle(hConnect); |
501 | 0 | WinHttpCloseHandle(hSession); |
502 | ||
503 | 0 | return true; |
504 | 0 | } |
505 | ||
506 | #elif LINUX | |
507 | ||
508 | static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { | |
509 | size_t realsize = size * nmemb; | |
510 | userp->append((char*)contents, realsize); | |
511 | return realsize; | |
512 | } | |
513 | ||
514 | bool HttpClient::get(const std::string& url, std::string& response) { | |
515 | CURL* curl; | |
516 | CURLcode res; | |
517 | ||
518 | curl = curl_easy_init(); | |
519 | if (!curl) { | |
520 | return false; | |
521 | } | |
522 | ||
523 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
524 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
525 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
526 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
527 | ||
528 | struct curl_slist* headers = NULL; | |
529 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
530 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
531 | ||
532 | res = curl_easy_perform(curl); | |
533 | ||
534 | curl_slist_free_all(headers); | |
535 | curl_easy_cleanup(curl); | |
536 | ||
537 | return (res == CURLE_OK); | |
538 | } | |
539 | ||
540 | bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { | |
541 | CURL* curl; | |
542 | CURLcode res; | |
543 | ||
544 | curl = curl_easy_init(); | |
545 | if (!curl) { | |
546 | return false; | |
547 | } | |
548 | ||
549 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
550 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
551 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
552 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
553 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
554 | ||
555 | struct curl_slist* headers = NULL; | |
556 | headers = curl_slist_append(headers, "Content-Type: application/json"); | |
557 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
558 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
559 | ||
560 | res = curl_easy_perform(curl); | |
561 | ||
562 | curl_slist_free_all(headers); | |
563 | curl_easy_cleanup(curl); | |
564 | ||
565 | return (res == CURLE_OK); | |
566 | } | |
567 | ||
568 | bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { | |
569 | CURL* curl; | |
570 | CURLcode res; | |
571 | ||
572 | curl = curl_easy_init(); | |
573 | if (!curl) { | |
574 | return false; | |
575 | } | |
576 | ||
577 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
578 | // PrivateBin API erwartet POST für delete | |
579 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
580 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
581 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
582 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); | |
583 | ||
584 | struct curl_slist* headers = NULL; | |
585 | headers = curl_slist_append(headers, "Content-Type: application/json"); | |
586 | headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); | |
587 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
588 | ||
589 | res = curl_easy_perform(curl); | |
590 | ||
591 | curl_slist_free_all(headers); | |
592 | curl_easy_cleanup(curl); | |
593 | ||
594 | return (res == CURLE_OK); | |
595 | } | |
596 | ||
597 | #endif |
Line | Count | Source |
1 | #include "json_parser.h" | |
2 | #include <iostream> | |
3 | #include <chrono> | |
4 | #include <cryptopp/base64.h> | |
5 | #include <cryptopp/filters.h> | |
6 | #include <cryptopp/queue.h> | |
7 | ||
8 | // Helper: Base64 encode without line breaks | |
9 | 6 | static std::string encode_base64(const std::vector<unsigned char>& data) { |
10 | 6 | std::string result; |
11 | 6 | CryptoPP::StringSource ss( |
12 | 6 | data.data(), data.size(), true, |
13 | 6 | new CryptoPP::Base64Encoder(new CryptoPP::StringSink(result), false /* insertLineBreaks */) |
14 | 6 | ); |
15 | 6 | return result; |
16 | 6 | } |
17 | ||
18 | // Helper: Base64 decode | |
19 | 3 | static std::vector<unsigned char> decode_base64(const std::string& b64) { |
20 | 3 | std::string decoded; |
21 | 3 | CryptoPP::StringSource ss( |
22 | 3 | b64, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decoded)) |
23 | 3 | ); |
24 | 3 | return std::vector<unsigned char>(decoded.begin(), decoded.end()); |
25 | 3 | } |
26 | ||
27 | json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text, | |
28 | const std::vector<unsigned char>& auth_tag, | |
29 | const std::vector<unsigned char>& iv, | |
30 | const std::vector<unsigned char>& salt, | |
31 | const std::string& expiration, | |
32 | const std::string& format, | |
33 | bool burn_after_reading, | |
34 | 2 | bool open_discussion) { |
35 | ||
36 | // Convert binary data to base64 strings | |
37 | 2 | std::string cipher_text_b64 = encode_base64(cipher_text); |
38 | 2 | std::string iv_b64 = encode_base64(iv); |
39 | 2 | std::string salt_b64 = encode_base64(salt); |
40 | ||
41 | // Hinweis: Zeitstempel wird aktuell nicht benötigt; entfernte Berechnung verhinderte eine | |
42 | // ungenutzte Variable unter Clang. | |
43 | ||
44 | // Create the metadata array according to PrivateBin v2 | |
45 | 2 | json metadata = { |
46 | 2 | { |
47 | 2 | iv_b64, // base64(cipher_iv) |
48 | 2 | salt_b64, // base64(kdf_salt) |
49 | 2 | 100000, // iterations |
50 | 2 | 256, // key size |
51 | 2 | 128, // tag size |
52 | 2 | "aes", // cipher |
53 | 2 | "gcm", // mode |
54 | 2 | "zlib" // compression |
55 | 2 | }, |
56 | 2 | format, // formatter key |
57 | 2 | burn_after_reading ? 1 : 0, // burn after reading |
58 | 2 | open_discussion ? 1 : 0 // open discussion |
59 | 2 | }; |
60 | ||
61 | // Create the main JSON structure | |
62 | // JSON close to v1.3 JSON-LD: meta.expire used by server | |
63 | 2 | json paste_json = { |
64 | 2 | {"v", 2}, // version |
65 | 2 | {"adata", metadata}, // metadata |
66 | 2 | {"ct", cipher_text_b64}, // ciphertext (base64) |
67 | 2 | {"meta", { |
68 | 2 | {"expire", expiration} |
69 | 2 | }} |
70 | 2 | }; |
71 | ||
72 | 2 | return paste_json; |
73 | 2 | } |
74 | ||
75 | bool JsonParser::parse_paste_json(const json& json_data, | |
76 | std::vector<unsigned char>& cipher_text, | |
77 | std::vector<unsigned char>& auth_tag, | |
78 | std::vector<unsigned char>& iv, | |
79 | std::vector<unsigned char>& salt, | |
80 | 1 | std::string& expiration) { |
81 | ||
82 | 1 | try { |
83 | // Extract and decode cipher text | |
84 | 1 | std::string ct_b64 = json_data.at("ct"); |
85 | 1 | cipher_text = decode_base64(ct_b64); |
86 | ||
87 | // Optional explicit tag field | |
88 | 1 | if (json_data.contains("tag")) { |
89 | 0 | std::string tag_b64 = json_data.at("tag"); |
90 | 0 | auth_tag = decode_base64(tag_b64); |
91 | 1 | } else { |
92 | 1 | auth_tag.clear(); |
93 | 1 | } |
94 | ||
95 | // Extract metadata and decode IV & salt | |
96 | 1 | json adata = json_data.at("adata"); |
97 | 1 | if (adata.size() >= 1) { |
98 | 1 | json first_element = adata[0]; |
99 | 1 | if (first_element.is_array() && first_element.size() >= 2) { |
100 | 1 | std::string iv_b64 = first_element[0]; |
101 | 1 | std::string salt_b64 = first_element[1]; |
102 | 1 | iv = decode_base64(iv_b64); |
103 | 1 | salt = decode_base64(salt_b64); |
104 | 1 | } |
105 | 1 | } |
106 | ||
107 | // Extract expiration; servers may return either meta.expire (string) | |
108 | // or meta.time_to_live (integer seconds). Both are optional for our use. | |
109 | 1 | try { |
110 | 1 | expiration = json_data.at("meta").at("expire"); |
111 | 1 | } catch (...) { |
112 | 0 | try { |
113 | 0 | auto ttl = json_data.at("meta").at("time_to_live").get<int>(); |
114 | 0 | expiration = std::to_string(ttl); |
115 | 0 | } catch (...) { |
116 | 0 | expiration.clear(); |
117 | 0 | } |
118 | 0 | } |
119 | ||
120 | 1 | return true; |
121 | 1 | } catch (...) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 1 | } |
125 | ||
126 | bool JsonParser::parse_response(const std::string& response, | |
127 | int& status, | |
128 | std::string& message, | |
129 | std::string& paste_id, | |
130 | std::string& url, | |
131 | 1 | std::string& delete_token) { |
132 | ||
133 | 1 | try { |
134 | 1 | json json_response = json::parse(response); |
135 | 1 | status = json_response.value("status", 1); |
136 | 1 | if (status == 0) { |
137 | 1 | paste_id = json_response.value("id", ""); |
138 | 1 | url = json_response.value("url", ""); |
139 | 1 | delete_token = json_response.value("deletetoken", ""); |
140 | 1 | } else { |
141 | 0 | message = json_response.value("message", ""); |
142 | 0 | } |
143 | ||
144 | 1 | return true; |
145 | 1 | } catch (...) { |
146 | 0 | return false; |
147 | 0 | } |
148 | 1 | } |
Line | Count | Source |
1 | #include "json_parser.h" | |
2 | #include <iostream> | |
3 | #include <chrono> | |
4 | #include <cryptopp/base64.h> | |
5 | #include <cryptopp/filters.h> | |
6 | #include <cryptopp/queue.h> | |
7 | ||
8 | // Helper: Base64 encode without line breaks | |
9 | 6 | static std::string encode_base64(const std::vector<unsigned char>& data) { |
10 | 6 | std::string result; |
11 | 6 | CryptoPP::StringSource ss( |
12 | 6 | data.data(), data.size(), true, |
13 | 6 | new CryptoPP::Base64Encoder(new CryptoPP::StringSink(result), false /* insertLineBreaks */) |
14 | 6 | ); |
15 | 6 | return result; |
16 | 6 | } |
17 | ||
18 | // Helper: Base64 decode | |
19 | 3 | static std::vector<unsigned char> decode_base64(const std::string& b64) { |
20 | 3 | std::string decoded; |
21 | 3 | CryptoPP::StringSource ss( |
22 | 3 | b64, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decoded)) |
23 | 3 | ); |
24 | 3 | return std::vector<unsigned char>(decoded.begin(), decoded.end()); |
25 | 3 | } |
26 | ||
27 | json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text, | |
28 | const std::vector<unsigned char>& auth_tag, | |
29 | const std::vector<unsigned char>& iv, | |
30 | const std::vector<unsigned char>& salt, | |
31 | const std::string& expiration, | |
32 | const std::string& format, | |
33 | bool burn_after_reading, | |
34 | 2 | bool open_discussion) { |
35 | ||
36 | // Convert binary data to base64 strings | |
37 | 2 | std::string cipher_text_b64 = encode_base64(cipher_text); |
38 | 2 | std::string iv_b64 = encode_base64(iv); |
39 | 2 | std::string salt_b64 = encode_base64(salt); |
40 | ||
41 | // Hinweis: Zeitstempel wird aktuell nicht benötigt; entfernte Berechnung verhinderte eine | |
42 | // ungenutzte Variable unter Clang. | |
43 | ||
44 | // Create the metadata array according to PrivateBin v2 | |
45 | 2 | json metadata = { |
46 | 2 | { |
47 | 2 | iv_b64, // base64(cipher_iv) |
48 | 2 | salt_b64, // base64(kdf_salt) |
49 | 2 | 100000, // iterations |
50 | 2 | 256, // key size |
51 | 2 | 128, // tag size |
52 | 2 | "aes", // cipher |
53 | 2 | "gcm", // mode |
54 | 2 | "zlib" // compression |
55 | 2 | }, |
56 | 2 | format, // formatter key |
57 | 2 | burn_after_reading ? 1 : 0, // burn after reading |
58 | 2 | open_discussion ? 1 : 0 // open discussion |
59 | 2 | }; |
60 | ||
61 | // Create the main JSON structure | |
62 | // JSON close to v1.3 JSON-LD: meta.expire used by server | |
63 | 2 | json paste_json = { |
64 | 2 | {"v", 2}, // version |
65 | 2 | {"adata", metadata}, // metadata |
66 | 2 | {"ct", cipher_text_b64}, // ciphertext (base64) |
67 | 2 | {"meta", { |
68 | 2 | {"expire", expiration} |
69 | 2 | }} |
70 | 2 | }; |
71 | ||
72 | 2 | return paste_json; |
73 | 2 | } |
74 | ||
75 | bool JsonParser::parse_paste_json(const json& json_data, | |
76 | std::vector<unsigned char>& cipher_text, | |
77 | std::vector<unsigned char>& auth_tag, | |
78 | std::vector<unsigned char>& iv, | |
79 | std::vector<unsigned char>& salt, | |
80 | 1 | std::string& expiration) { |
81 | ||
82 | 1 | try { |
83 | // Extract and decode cipher text | |
84 | 1 | std::string ct_b64 = json_data.at("ct"); |
85 | 1 | cipher_text = decode_base64(ct_b64); |
86 | ||
87 | // Optional explicit tag field | |
88 | 1 | if (json_data.contains("tag")) { |
89 | 0 | std::string tag_b64 = json_data.at("tag"); |
90 | 0 | auth_tag = decode_base64(tag_b64); |
91 | 1 | } else { |
92 | 1 | auth_tag.clear(); |
93 | 1 | } |
94 | ||
95 | // Extract metadata and decode IV & salt | |
96 | 1 | json adata = json_data.at("adata"); |
97 | 1 | if (adata.size() >= 1) { |
98 | 1 | json first_element = adata[0]; |
99 | 1 | if (first_element.is_array() && first_element.size() >= 2) { |
100 | 1 | std::string iv_b64 = first_element[0]; |
101 | 1 | std::string salt_b64 = first_element[1]; |
102 | 1 | iv = decode_base64(iv_b64); |
103 | 1 | salt = decode_base64(salt_b64); |
104 | 1 | } |
105 | 1 | } |
106 | ||
107 | // Extract expiration; servers may return either meta.expire (string) | |
108 | // or meta.time_to_live (integer seconds). Both are optional for our use. | |
109 | 1 | try { |
110 | 1 | expiration = json_data.at("meta").at("expire"); |
111 | 1 | } catch (...) { |
112 | 0 | try { |
113 | 0 | auto ttl = json_data.at("meta").at("time_to_live").get<int>(); |
114 | 0 | expiration = std::to_string(ttl); |
115 | 0 | } catch (...) { |
116 | 0 | expiration.clear(); |
117 | 0 | } |
118 | 0 | } |
119 | ||
120 | 1 | return true; |
121 | 1 | } catch (...) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 1 | } |
125 | ||
126 | bool JsonParser::parse_response(const std::string& response, | |
127 | int& status, | |
128 | std::string& message, | |
129 | std::string& paste_id, | |
130 | std::string& url, | |
131 | 1 | std::string& delete_token) { |
132 | ||
133 | 1 | try { |
134 | 1 | json json_response = json::parse(response); |
135 | 1 | status = json_response.value("status", 1); |
136 | 1 | if (status == 0) { |
137 | 1 | paste_id = json_response.value("id", ""); |
138 | 1 | url = json_response.value("url", ""); |
139 | 1 | delete_token = json_response.value("deletetoken", ""); |
140 | 1 | } else { |
141 | 0 | message = json_response.value("message", ""); |
142 | 0 | } |
143 | ||
144 | 1 | return true; |
145 | 1 | } catch (...) { |
146 | 0 | return false; |
147 | 0 | } |
148 | 1 | } |
Line | Count | Source |
1 | #include "privatebinapi.h" | |
2 | #include "http_client.h" | |
3 | #include "crypto.h" | |
4 | #include "json_parser.h" | |
5 | #include "base58.h" | |
6 | #include <string> | |
7 | #include <vector> | |
8 | #include <cstring> | |
9 | #include <cstdlib> | |
10 | #include <iostream> | |
11 | #include <fstream> | |
12 | #include <sstream> | |
13 | ||
14 | #define PRIVATEBIN_API_VERSION "1.3" | |
15 | ||
16 | // Error codes | |
17 | 0 | #define ERROR_SUCCESS 0 |
18 | 3 | #define ERROR_NETWORK 1 |
19 | 0 | #define ERROR_CRYPTO 2 |
20 | 0 | #define ERROR_INVALID_INPUT 3 |
21 | 0 | #define ERROR_SERVER 4 |
22 | 0 | #define ERROR_JSON_PARSE 5 |
23 | ||
24 | // Helper function to copy string to output parameter | |
25 | 0 | static void copy_string_to_output(const std::string& source, char** destination) { |
26 | 0 | if (destination) { |
27 | 0 | *destination = static_cast<char*>(malloc(source.length() + 1)); |
28 | 0 | if (*destination) { |
29 | 0 | strcpy_s(*destination, source.length() + 1, source.c_str()); |
30 | 0 | } |
31 | 0 | } |
32 | 0 | } |
33 | ||
34 | extern "C" { | |
35 | ||
36 | PRIVATEBIN_API int create_paste(const char* server_url, const char* content, | |
37 | const char* password, const char* expiration, | |
38 | const char* format, int burn_after_reading, | |
39 | int open_discussion, char** paste_url, | |
40 | 1 | char** delete_token) { |
41 | ||
42 | 1 | if (!server_url || !content) { |
43 | 0 | return ERROR_INVALID_INPUT; |
44 | 0 | } |
45 | ||
46 | 1 | try { |
47 | // Generate a random 32-byte paste key | |
48 | 1 | std::vector<unsigned char> paste_key = Crypto::generate_key(32); |
49 | ||
50 | // If password provided, append it to the paste key | |
51 | 1 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
52 | 1 | if (password) { |
53 | 0 | paste_passphrase += password; |
54 | 0 | } |
55 | ||
56 | // Convert content to bytes | |
57 | 1 | std::vector<unsigned char> plaintext(content, content + strlen(content)); |
58 | ||
59 | // Compress the plaintext | |
60 | 1 | std::cerr << "[privatebinapi] compress..." << std::endl; |
61 | 1 | std::vector<unsigned char> compressed_data = Crypto::compress(plaintext); |
62 | ||
63 | // Generate random salt and IV | |
64 | 1 | std::vector<unsigned char> salt = Crypto::generate_key(8); |
65 | 1 | std::vector<unsigned char> iv = Crypto::generate_key(16); |
66 | ||
67 | // Derive key using PBKDF2 | |
68 | 1 | std::cerr << "[privatebinapi] pbkdf2..." << std::endl; |
69 | 1 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
70 | 1 | paste_passphrase, salt, 100000, 32); |
71 | ||
72 | // Encrypt the data | |
73 | 1 | std::cerr << "[privatebinapi] encrypt..." << std::endl; |
74 | 1 | std::vector<unsigned char> auth_tag; |
75 | 1 | std::vector<unsigned char> cipher_text = Crypto::encrypt( |
76 | 1 | compressed_data, derived_key, iv, auth_tag); |
77 | ||
78 | // Create the JSON structure | |
79 | 1 | json paste_json = JsonParser::create_paste_json( |
80 | 1 | cipher_text, auth_tag, iv, salt, |
81 | 1 | expiration ? expiration : "1day", |
82 | 1 | format ? format : "plaintext", |
83 | 1 | burn_after_reading != 0, |
84 | 1 | open_discussion != 0); |
85 | ||
86 | // Serialize JSON | |
87 | 1 | std::string json_data = paste_json.dump(); |
88 | ||
89 | // Send POST request | |
90 | 1 | HttpClient client; |
91 | 1 | std::string response; |
92 | 1 | if (!client.post(server_url, json_data, response)) { |
93 | 1 | return ERROR_NETWORK; |
94 | 1 | } |
95 | ||
96 | // Parse response | |
97 | 0 | int status; |
98 | 0 | std::string message, paste_id, url, del_token; |
99 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) { |
100 | 0 | std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl; |
101 | 0 | return ERROR_JSON_PARSE; |
102 | 0 | } |
103 | 0 | if (status != 0) { |
104 | 0 | std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl; |
105 | 0 | } |
106 | ||
107 | 0 | if (status != 0) { |
108 | 0 | return ERROR_SERVER; |
109 | 0 | } |
110 | ||
111 | // Encode the paste key with Base58 | |
112 | 0 | std::string encoded_key = Base58::encode(paste_key); |
113 | ||
114 | // Construct the full URL | |
115 | 0 | std::string full_url = url + "#" + encoded_key; |
116 | ||
117 | // Copy results to output parameters | |
118 | 0 | copy_string_to_output(full_url, paste_url); |
119 | 0 | copy_string_to_output(del_token, delete_token); |
120 | ||
121 | 0 | return ERROR_SUCCESS; |
122 | 0 | } catch (const std::exception& e) { |
123 | 0 | std::cerr << "[privatebinapi] crypto error: " << e.what() << std::endl; |
124 | 0 | return ERROR_CRYPTO; |
125 | 0 | } catch (...) { |
126 | 0 | std::cerr << "[privatebinapi] unknown crypto error" << std::endl; |
127 | 0 | return ERROR_CRYPTO; |
128 | 0 | } |
129 | 1 | } |
130 | ||
131 | PRIVATEBIN_API int upload_file(const char* server_url, const char* file_path, | |
132 | const char* password, const char* expiration, | |
133 | int burn_after_reading, int open_discussion, | |
134 | 0 | char** paste_url, char** delete_token) { |
135 | ||
136 | 0 | if (!server_url || !file_path) { |
137 | 0 | return ERROR_INVALID_INPUT; |
138 | 0 | } |
139 | ||
140 | 0 | try { |
141 | // Read the file content | |
142 | 0 | std::ifstream file(file_path, std::ios::binary); |
143 | 0 | if (!file.is_open()) { |
144 | 0 | std::cerr << "[privatebinapi] Failed to open file: " << file_path << std::endl; |
145 | 0 | return ERROR_INVALID_INPUT; |
146 | 0 | } |
147 | ||
148 | // Get file size | |
149 | 0 | file.seekg(0, std::ios::end); |
150 | 0 | std::streamsize file_size = file.tellg(); |
151 | 0 | file.seekg(0, std::ios::beg); |
152 | ||
153 | // Check if file is too large (PrivateBin typically has limits) | |
154 | 0 | if (file_size > 100 * 1024 * 1024) { // 100MB limit |
155 | 0 | std::cerr << "[privatebinapi] File too large: " << file_size << " bytes" << std::endl; |
156 | 0 | return ERROR_INVALID_INPUT; |
157 | 0 | } |
158 | ||
159 | // Read file content into vector | |
160 | 0 | std::vector<unsigned char> file_content(file_size); |
161 | 0 | if (!file.read(reinterpret_cast<char*>(file_content.data()), file_size)) { |
162 | 0 | std::cerr << "[privatebinapi] Failed to read file content" << std::endl; |
163 | 0 | return ERROR_INVALID_INPUT; |
164 | 0 | } |
165 | 0 | file.close(); |
166 | ||
167 | // Generate a random 32-byte paste key | |
168 | 0 | std::vector<unsigned char> paste_key = Crypto::generate_key(32); |
169 | ||
170 | // If password provided, append it to the paste key | |
171 | 0 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
172 | 0 | if (password) { |
173 | 0 | paste_passphrase += password; |
174 | 0 | } |
175 | ||
176 | // Compress the file content | |
177 | 0 | std::cerr << "[privatebinapi] compress file..." << std::endl; |
178 | 0 | std::vector<unsigned char> compressed_data = Crypto::compress(file_content); |
179 | ||
180 | // Generate random salt and IV | |
181 | 0 | std::vector<unsigned char> salt = Crypto::generate_key(8); |
182 | 0 | std::vector<unsigned char> iv = Crypto::generate_key(16); |
183 | ||
184 | // Derive key using PBKDF2 | |
185 | 0 | std::cerr << "[privatebinapi] pbkdf2..." << std::endl; |
186 | 0 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
187 | 0 | paste_passphrase, salt, 100000, 32); |
188 | ||
189 | // Encrypt the data | |
190 | 0 | std::cerr << "[privatebinapi] encrypt file..." << std::endl; |
191 | 0 | std::vector<unsigned char> auth_tag; |
192 | 0 | std::vector<unsigned char> cipher_text = Crypto::encrypt( |
193 | 0 | compressed_data, derived_key, iv, auth_tag); |
194 | ||
195 | // Create the JSON structure for file upload | |
196 | 0 | json paste_json = JsonParser::create_paste_json( |
197 | 0 | cipher_text, auth_tag, iv, salt, |
198 | 0 | expiration ? expiration : "1day", |
199 | 0 | "plaintext", // Files are treated as plaintext |
200 | 0 | burn_after_reading != 0, |
201 | 0 | open_discussion != 0); |
202 | ||
203 | // Note: File metadata is not added to avoid server compatibility issues | |
204 | // The file content is encrypted and uploaded as a binary paste | |
205 | ||
206 | // Serialize JSON | |
207 | 0 | std::string json_data = paste_json.dump(); |
208 | ||
209 | // Send POST request | |
210 | 0 | HttpClient client; |
211 | 0 | std::string response; |
212 | 0 | if (!client.post(server_url, json_data, response)) { |
213 | 0 | return ERROR_NETWORK; |
214 | 0 | } |
215 | ||
216 | // Parse response | |
217 | 0 | int status; |
218 | 0 | std::string message, paste_id, url, del_token; |
219 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) { |
220 | 0 | std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl; |
221 | 0 | return ERROR_JSON_PARSE; |
222 | 0 | } |
223 | 0 | if (status != 0) { |
224 | 0 | std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl; |
225 | 0 | } |
226 | ||
227 | 0 | if (status != 0) { |
228 | 0 | return ERROR_SERVER; |
229 | 0 | } |
230 | ||
231 | // Encode the paste key with Base58 | |
232 | 0 | std::string encoded_key = Base58::encode(paste_key); |
233 | ||
234 | // Construct the full URL | |
235 | 0 | std::string full_url = url + "#" + encoded_key; |
236 | ||
237 | // Copy results to output parameters | |
238 | 0 | copy_string_to_output(full_url, paste_url); |
239 | 0 | copy_string_to_output(del_token, delete_token); |
240 | ||
241 | 0 | return ERROR_SUCCESS; |
242 | 0 | } catch (const std::exception& e) { |
243 | 0 | std::cerr << "[privatebinapi] file upload error: " << e.what() << std::endl; |
244 | 0 | return ERROR_CRYPTO; |
245 | 0 | } catch (...) { |
246 | 0 | std::cerr << "[privatebinapi] unknown file upload error" << std::endl; |
247 | 0 | return ERROR_CRYPTO; |
248 | 0 | } |
249 | 0 | } |
250 | ||
251 | PRIVATEBIN_API int get_paste(const char* server_url, const char* paste_id, | |
252 | 1 | const char* key, char** content) { |
253 | ||
254 | 1 | if (!server_url || !paste_id || !key || !content) { |
255 | 0 | return ERROR_INVALID_INPUT; |
256 | 0 | } |
257 | ||
258 | 1 | try { |
259 | // Construct the URL with query per API: base?pasteID | |
260 | 1 | std::string url = std::string(server_url) + "?" + paste_id; |
261 | ||
262 | // Send GET request | |
263 | 1 | HttpClient client; |
264 | 1 | std::string response; |
265 | 1 | std::cerr << "[privatebinapi] GET " << url << std::endl; |
266 | 1 | if (!client.get(url, response)) { |
267 | 1 | return ERROR_NETWORK; |
268 | 1 | } |
269 | 0 | std::cerr << "[privatebinapi] GET response: " << response << std::endl; |
270 | ||
271 | // Parse the JSON response | |
272 | 0 | json json_data = json::parse(response); |
273 | ||
274 | // Extract encrypted data | |
275 | 0 | std::vector<unsigned char> cipher_text, auth_tag, iv, salt; |
276 | 0 | std::string expiration; |
277 | 0 | if (!JsonParser::parse_paste_json(json_data, cipher_text, auth_tag, iv, salt, expiration)) { |
278 | 0 | return ERROR_JSON_PARSE; |
279 | 0 | } |
280 | ||
281 | // Decode the key from Base58 | |
282 | 0 | std::vector<unsigned char> paste_key = Base58::decode(key); |
283 | ||
284 | // Derive key using PBKDF2 | |
285 | 0 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
286 | 0 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
287 | 0 | paste_passphrase, salt, 100000, 32); |
288 | ||
289 | // Decrypt the data | |
290 | 0 | std::vector<unsigned char> compressed_data = Crypto::decrypt( |
291 | 0 | cipher_text, derived_key, iv, auth_tag); |
292 | ||
293 | // Decompress the data | |
294 | 0 | std::vector<unsigned char> plaintext = Crypto::decompress(compressed_data); |
295 | ||
296 | // Convert to string | |
297 | 0 | std::string result(reinterpret_cast<char*>(plaintext.data()), plaintext.size()); |
298 | ||
299 | // Copy result to output parameter | |
300 | 0 | copy_string_to_output(result, content); |
301 | ||
302 | 0 | return ERROR_SUCCESS; |
303 | 0 | } catch (...) { |
304 | 0 | return ERROR_CRYPTO; |
305 | 0 | } |
306 | 1 | } |
307 | ||
308 | PRIVATEBIN_API int delete_paste(const char* server_url, const char* paste_id, | |
309 | 1 | const char* delete_token) { |
310 | ||
311 | 1 | if (!server_url || !paste_id || !delete_token) { |
312 | 0 | return ERROR_INVALID_INPUT; |
313 | 0 | } |
314 | ||
315 | 1 | try { |
316 | // Create the JSON payload | |
317 | 1 | json payload = { |
318 | 1 | {"pasteid", paste_id}, |
319 | 1 | {"deletetoken", delete_token} |
320 | 1 | }; |
321 | ||
322 | 1 | std::string json_data = payload.dump(); |
323 | ||
324 | // Send DELETE request | |
325 | 1 | HttpClient client; |
326 | 1 | std::string response; |
327 | 1 | std::cerr << "[privatebinapi] DELETE payload: " << json_data << std::endl; |
328 | 1 | if (!client.delete_req(server_url, json_data, response)) { |
329 | 1 | return ERROR_NETWORK; |
330 | 1 | } |
331 | 0 | std::cerr << "[privatebinapi] DELETE response: " << response << std::endl; |
332 | ||
333 | // Parse response | |
334 | 0 | int status; |
335 | 0 | std::string message, paste_id_result, url, del_token; |
336 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id_result, url, del_token)) { |
337 | 0 | return ERROR_JSON_PARSE; |
338 | 0 | } |
339 | ||
340 | 0 | if (status != 0) { |
341 | 0 | return ERROR_SERVER; |
342 | 0 | } |
343 | ||
344 | 0 | return ERROR_SUCCESS; |
345 | 0 | } catch (...) { |
346 | 0 | return ERROR_CRYPTO; |
347 | 0 | } |
348 | 1 | } |
349 | ||
350 | 0 | PRIVATEBIN_API void free_string(char* str) { |
351 | 0 | if (str) { |
352 | 0 | free(str); |
353 | 0 | } |
354 | 0 | } |
355 | ||
356 | } // extern "C" |
Line | Count | Source |
1 | #include "privatebinapi.h" | |
2 | #include "http_client.h" | |
3 | #include "crypto.h" | |
4 | #include "json_parser.h" | |
5 | #include "base58.h" | |
6 | #include <string> | |
7 | #include <vector> | |
8 | #include <cstring> | |
9 | #include <cstdlib> | |
10 | #include <iostream> | |
11 | #include <fstream> | |
12 | #include <sstream> | |
13 | ||
14 | #define PRIVATEBIN_API_VERSION "1.3" | |
15 | ||
16 | // Error codes | |
17 | 0 | #define ERROR_SUCCESS 0 |
18 | 3 | #define ERROR_NETWORK 1 |
19 | 0 | #define ERROR_CRYPTO 2 |
20 | 0 | #define ERROR_INVALID_INPUT 3 |
21 | 0 | #define ERROR_SERVER 4 |
22 | 0 | #define ERROR_JSON_PARSE 5 |
23 | ||
24 | // Helper function to copy string to output parameter | |
25 | 0 | static void copy_string_to_output(const std::string& source, char** destination) { |
26 | 0 | if (destination) { |
27 | 0 | *destination = static_cast<char*>(malloc(source.length() + 1)); |
28 | 0 | if (*destination) { |
29 | 0 | strcpy_s(*destination, source.length() + 1, source.c_str()); |
30 | 0 | } |
31 | 0 | } |
32 | 0 | } |
33 | ||
34 | extern "C" { | |
35 | ||
36 | PRIVATEBIN_API int create_paste(const char* server_url, const char* content, | |
37 | const char* password, const char* expiration, | |
38 | const char* format, int burn_after_reading, | |
39 | int open_discussion, char** paste_url, | |
40 | 1 | char** delete_token) { |
41 | ||
42 | 1 | if (!server_url || !content) { |
43 | 0 | return ERROR_INVALID_INPUT; |
44 | 0 | } |
45 | ||
46 | 1 | try { |
47 | // Generate a random 32-byte paste key | |
48 | 1 | std::vector<unsigned char> paste_key = Crypto::generate_key(32); |
49 | ||
50 | // If password provided, append it to the paste key | |
51 | 1 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
52 | 1 | if (password) { |
53 | 0 | paste_passphrase += password; |
54 | 0 | } |
55 | ||
56 | // Convert content to bytes | |
57 | 1 | std::vector<unsigned char> plaintext(content, content + strlen(content)); |
58 | ||
59 | // Compress the plaintext | |
60 | 1 | std::cerr << "[privatebinapi] compress..." << std::endl; |
61 | 1 | std::vector<unsigned char> compressed_data = Crypto::compress(plaintext); |
62 | ||
63 | // Generate random salt and IV | |
64 | 1 | std::vector<unsigned char> salt = Crypto::generate_key(8); |
65 | 1 | std::vector<unsigned char> iv = Crypto::generate_key(16); |
66 | ||
67 | // Derive key using PBKDF2 | |
68 | 1 | std::cerr << "[privatebinapi] pbkdf2..." << std::endl; |
69 | 1 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
70 | 1 | paste_passphrase, salt, 100000, 32); |
71 | ||
72 | // Encrypt the data | |
73 | 1 | std::cerr << "[privatebinapi] encrypt..." << std::endl; |
74 | 1 | std::vector<unsigned char> auth_tag; |
75 | 1 | std::vector<unsigned char> cipher_text = Crypto::encrypt( |
76 | 1 | compressed_data, derived_key, iv, auth_tag); |
77 | ||
78 | // Create the JSON structure | |
79 | 1 | json paste_json = JsonParser::create_paste_json( |
80 | 1 | cipher_text, auth_tag, iv, salt, |
81 | 1 | expiration ? expiration : "1day", |
82 | 1 | format ? format : "plaintext", |
83 | 1 | burn_after_reading != 0, |
84 | 1 | open_discussion != 0); |
85 | ||
86 | // Serialize JSON | |
87 | 1 | std::string json_data = paste_json.dump(); |
88 | ||
89 | // Send POST request | |
90 | 1 | HttpClient client; |
91 | 1 | std::string response; |
92 | 1 | if (!client.post(server_url, json_data, response)) { |
93 | 1 | return ERROR_NETWORK; |
94 | 1 | } |
95 | ||
96 | // Parse response | |
97 | 0 | int status; |
98 | 0 | std::string message, paste_id, url, del_token; |
99 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) { |
100 | 0 | std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl; |
101 | 0 | return ERROR_JSON_PARSE; |
102 | 0 | } |
103 | 0 | if (status != 0) { |
104 | 0 | std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl; |
105 | 0 | } |
106 | ||
107 | 0 | if (status != 0) { |
108 | 0 | return ERROR_SERVER; |
109 | 0 | } |
110 | ||
111 | // Encode the paste key with Base58 | |
112 | 0 | std::string encoded_key = Base58::encode(paste_key); |
113 | ||
114 | // Construct the full URL | |
115 | 0 | std::string full_url = url + "#" + encoded_key; |
116 | ||
117 | // Copy results to output parameters | |
118 | 0 | copy_string_to_output(full_url, paste_url); |
119 | 0 | copy_string_to_output(del_token, delete_token); |
120 | ||
121 | 0 | return ERROR_SUCCESS; |
122 | 0 | } catch (const std::exception& e) { |
123 | 0 | std::cerr << "[privatebinapi] crypto error: " << e.what() << std::endl; |
124 | 0 | return ERROR_CRYPTO; |
125 | 0 | } catch (...) { |
126 | 0 | std::cerr << "[privatebinapi] unknown crypto error" << std::endl; |
127 | 0 | return ERROR_CRYPTO; |
128 | 0 | } |
129 | 1 | } |
130 | ||
131 | PRIVATEBIN_API int upload_file(const char* server_url, const char* file_path, | |
132 | const char* password, const char* expiration, | |
133 | int burn_after_reading, int open_discussion, | |
134 | 0 | char** paste_url, char** delete_token) { |
135 | ||
136 | 0 | if (!server_url || !file_path) { |
137 | 0 | return ERROR_INVALID_INPUT; |
138 | 0 | } |
139 | ||
140 | 0 | try { |
141 | // Read the file content | |
142 | 0 | std::ifstream file(file_path, std::ios::binary); |
143 | 0 | if (!file.is_open()) { |
144 | 0 | std::cerr << "[privatebinapi] Failed to open file: " << file_path << std::endl; |
145 | 0 | return ERROR_INVALID_INPUT; |
146 | 0 | } |
147 | ||
148 | // Get file size | |
149 | 0 | file.seekg(0, std::ios::end); |
150 | 0 | std::streamsize file_size = file.tellg(); |
151 | 0 | file.seekg(0, std::ios::beg); |
152 | ||
153 | // Check if file is too large (PrivateBin typically has limits) | |
154 | 0 | if (file_size > 100 * 1024 * 1024) { // 100MB limit |
155 | 0 | std::cerr << "[privatebinapi] File too large: " << file_size << " bytes" << std::endl; |
156 | 0 | return ERROR_INVALID_INPUT; |
157 | 0 | } |
158 | ||
159 | // Read file content into vector | |
160 | 0 | std::vector<unsigned char> file_content(file_size); |
161 | 0 | if (!file.read(reinterpret_cast<char*>(file_content.data()), file_size)) { |
162 | 0 | std::cerr << "[privatebinapi] Failed to read file content" << std::endl; |
163 | 0 | return ERROR_INVALID_INPUT; |
164 | 0 | } |
165 | 0 | file.close(); |
166 | ||
167 | // Generate a random 32-byte paste key | |
168 | 0 | std::vector<unsigned char> paste_key = Crypto::generate_key(32); |
169 | ||
170 | // If password provided, append it to the paste key | |
171 | 0 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
172 | 0 | if (password) { |
173 | 0 | paste_passphrase += password; |
174 | 0 | } |
175 | ||
176 | // Compress the file content | |
177 | 0 | std::cerr << "[privatebinapi] compress file..." << std::endl; |
178 | 0 | std::vector<unsigned char> compressed_data = Crypto::compress(file_content); |
179 | ||
180 | // Generate random salt and IV | |
181 | 0 | std::vector<unsigned char> salt = Crypto::generate_key(8); |
182 | 0 | std::vector<unsigned char> iv = Crypto::generate_key(16); |
183 | ||
184 | // Derive key using PBKDF2 | |
185 | 0 | std::cerr << "[privatebinapi] pbkdf2..." << std::endl; |
186 | 0 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
187 | 0 | paste_passphrase, salt, 100000, 32); |
188 | ||
189 | // Encrypt the data | |
190 | 0 | std::cerr << "[privatebinapi] encrypt file..." << std::endl; |
191 | 0 | std::vector<unsigned char> auth_tag; |
192 | 0 | std::vector<unsigned char> cipher_text = Crypto::encrypt( |
193 | 0 | compressed_data, derived_key, iv, auth_tag); |
194 | ||
195 | // Create the JSON structure for file upload | |
196 | 0 | json paste_json = JsonParser::create_paste_json( |
197 | 0 | cipher_text, auth_tag, iv, salt, |
198 | 0 | expiration ? expiration : "1day", |
199 | 0 | "plaintext", // Files are treated as plaintext |
200 | 0 | burn_after_reading != 0, |
201 | 0 | open_discussion != 0); |
202 | ||
203 | // Note: File metadata is not added to avoid server compatibility issues | |
204 | // The file content is encrypted and uploaded as a binary paste | |
205 | ||
206 | // Serialize JSON | |
207 | 0 | std::string json_data = paste_json.dump(); |
208 | ||
209 | // Send POST request | |
210 | 0 | HttpClient client; |
211 | 0 | std::string response; |
212 | 0 | if (!client.post(server_url, json_data, response)) { |
213 | 0 | return ERROR_NETWORK; |
214 | 0 | } |
215 | ||
216 | // Parse response | |
217 | 0 | int status; |
218 | 0 | std::string message, paste_id, url, del_token; |
219 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) { |
220 | 0 | std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl; |
221 | 0 | return ERROR_JSON_PARSE; |
222 | 0 | } |
223 | 0 | if (status != 0) { |
224 | 0 | std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl; |
225 | 0 | } |
226 | ||
227 | 0 | if (status != 0) { |
228 | 0 | return ERROR_SERVER; |
229 | 0 | } |
230 | ||
231 | // Encode the paste key with Base58 | |
232 | 0 | std::string encoded_key = Base58::encode(paste_key); |
233 | ||
234 | // Construct the full URL | |
235 | 0 | std::string full_url = url + "#" + encoded_key; |
236 | ||
237 | // Copy results to output parameters | |
238 | 0 | copy_string_to_output(full_url, paste_url); |
239 | 0 | copy_string_to_output(del_token, delete_token); |
240 | ||
241 | 0 | return ERROR_SUCCESS; |
242 | 0 | } catch (const std::exception& e) { |
243 | 0 | std::cerr << "[privatebinapi] file upload error: " << e.what() << std::endl; |
244 | 0 | return ERROR_CRYPTO; |
245 | 0 | } catch (...) { |
246 | 0 | std::cerr << "[privatebinapi] unknown file upload error" << std::endl; |
247 | 0 | return ERROR_CRYPTO; |
248 | 0 | } |
249 | 0 | } |
250 | ||
251 | PRIVATEBIN_API int get_paste(const char* server_url, const char* paste_id, | |
252 | 1 | const char* key, char** content) { |
253 | ||
254 | 1 | if (!server_url || !paste_id || !key || !content) { |
255 | 0 | return ERROR_INVALID_INPUT; |
256 | 0 | } |
257 | ||
258 | 1 | try { |
259 | // Construct the URL with query per API: base?pasteID | |
260 | 1 | std::string url = std::string(server_url) + "?" + paste_id; |
261 | ||
262 | // Send GET request | |
263 | 1 | HttpClient client; |
264 | 1 | std::string response; |
265 | 1 | std::cerr << "[privatebinapi] GET " << url << std::endl; |
266 | 1 | if (!client.get(url, response)) { |
267 | 1 | return ERROR_NETWORK; |
268 | 1 | } |
269 | 0 | std::cerr << "[privatebinapi] GET response: " << response << std::endl; |
270 | ||
271 | // Parse the JSON response | |
272 | 0 | json json_data = json::parse(response); |
273 | ||
274 | // Extract encrypted data | |
275 | 0 | std::vector<unsigned char> cipher_text, auth_tag, iv, salt; |
276 | 0 | std::string expiration; |
277 | 0 | if (!JsonParser::parse_paste_json(json_data, cipher_text, auth_tag, iv, salt, expiration)) { |
278 | 0 | return ERROR_JSON_PARSE; |
279 | 0 | } |
280 | ||
281 | // Decode the key from Base58 | |
282 | 0 | std::vector<unsigned char> paste_key = Base58::decode(key); |
283 | ||
284 | // Derive key using PBKDF2 | |
285 | 0 | std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size()); |
286 | 0 | std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256( |
287 | 0 | paste_passphrase, salt, 100000, 32); |
288 | ||
289 | // Decrypt the data | |
290 | 0 | std::vector<unsigned char> compressed_data = Crypto::decrypt( |
291 | 0 | cipher_text, derived_key, iv, auth_tag); |
292 | ||
293 | // Decompress the data | |
294 | 0 | std::vector<unsigned char> plaintext = Crypto::decompress(compressed_data); |
295 | ||
296 | // Convert to string | |
297 | 0 | std::string result(reinterpret_cast<char*>(plaintext.data()), plaintext.size()); |
298 | ||
299 | // Copy result to output parameter | |
300 | 0 | copy_string_to_output(result, content); |
301 | ||
302 | 0 | return ERROR_SUCCESS; |
303 | 0 | } catch (...) { |
304 | 0 | return ERROR_CRYPTO; |
305 | 0 | } |
306 | 1 | } |
307 | ||
308 | PRIVATEBIN_API int delete_paste(const char* server_url, const char* paste_id, | |
309 | 1 | const char* delete_token) { |
310 | ||
311 | 1 | if (!server_url || !paste_id || !delete_token) { |
312 | 0 | return ERROR_INVALID_INPUT; |
313 | 0 | } |
314 | ||
315 | 1 | try { |
316 | // Create the JSON payload | |
317 | 1 | json payload = { |
318 | 1 | {"pasteid", paste_id}, |
319 | 1 | {"deletetoken", delete_token} |
320 | 1 | }; |
321 | ||
322 | 1 | std::string json_data = payload.dump(); |
323 | ||
324 | // Send DELETE request | |
325 | 1 | HttpClient client; |
326 | 1 | std::string response; |
327 | 1 | std::cerr << "[privatebinapi] DELETE payload: " << json_data << std::endl; |
328 | 1 | if (!client.delete_req(server_url, json_data, response)) { |
329 | 1 | return ERROR_NETWORK; |
330 | 1 | } |
331 | 0 | std::cerr << "[privatebinapi] DELETE response: " << response << std::endl; |
332 | ||
333 | // Parse response | |
334 | 0 | int status; |
335 | 0 | std::string message, paste_id_result, url, del_token; |
336 | 0 | if (!JsonParser::parse_response(response, status, message, paste_id_result, url, del_token)) { |
337 | 0 | return ERROR_JSON_PARSE; |
338 | 0 | } |
339 | ||
340 | 0 | if (status != 0) { |
341 | 0 | return ERROR_SERVER; |
342 | 0 | } |
343 | ||
344 | 0 | return ERROR_SUCCESS; |
345 | 0 | } catch (...) { |
346 | 0 | return ERROR_CRYPTO; |
347 | 0 | } |
348 | 1 | } |
349 | ||
350 | 0 | PRIVATEBIN_API void free_string(char* str) { |
351 | 0 | if (str) { |
352 | 0 | free(str); |
353 | 0 | } |
354 | 0 | } |
355 | ||
356 | } // extern "C" |
Click here for information about interpreting this report.
| Filename | Function Coverage | Line Coverage | Region Coverage | Branch Coverage |
base58.cpp | 0.00% (0/2) | 0.00% (0/67) | 0.00% (0/48) | 0.00% (0/34) |
crypto.cpp | 66.67% (4/6) | 56.19% (59/105) | 36.84% (7/19) | 0.00% (0/2) |
http_client.cpp | 100.00% (5/5) | 51.67% (186/360) | 66.67% (170/255) | 41.38% (48/116) |
json_parser.cpp | 100.00% (5/5) | 84.21% (80/95) | 71.88% (23/32) | 50.00% (7/14) |
privatebinapi.cpp | 50.00% (3/6) | 27.01% (57/211) | 28.15% (38/135) | 21.43% (15/70) |
Totals | 70.83% (17/24) | 45.58% (382/838) | 48.67% (238/489) | 29.66% (70/236) |
Click here for information about interpreting this report.
| Filename | Function Coverage | Line Coverage | Region Coverage | Branch Coverage |
base58.cpp | 0.00% (0/2) | 0.00% (0/67) | 0.00% (0/48) | 0.00% (0/34) |
crypto.cpp | 66.67% (4/6) | 56.19% (59/105) | 36.84% (7/19) | 0.00% (0/2) |
http_client.cpp | 100.00% (5/5) | 51.67% (186/360) | 66.67% (170/255) | 41.38% (48/116) |
json_parser.cpp | 100.00% (5/5) | 84.21% (80/95) | 71.88% (23/32) | 50.00% (7/14) |
privatebinapi.cpp | 50.00% (3/6) | 27.01% (57/211) | 28.15% (38/135) | 21.43% (15/70) |
Totals | 70.83% (17/24) | 45.58% (382/838) | 48.67% (238/489) | 29.66% (70/236) |