diff --git a/build-clang/Testing/Temporary/CTestCostData.txt b/build-clang/Testing/Temporary/CTestCostData.txt index f7f7dfd..4bd7b77 100644 --- a/build-clang/Testing/Temporary/CTestCostData.txt +++ b/build-clang/Testing/Temporary/CTestCostData.txt @@ -1,4 +1,3 @@ -test_basic 12 0.570438 +test_basic 13 0.51498 example_run 0 0 --- -example_run 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 7794056..8f26197 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 @@ -

Coverage Report

Created: 2025-08-29 11:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\base58.cpp
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
}
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\base58.cpp
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
}
\ No newline at end of file diff --git a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/crypto.cpp.html b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/crypto.cpp.html index 9638c8a..7892c10 100644 --- a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/crypto.cpp.html +++ b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/crypto.cpp.html @@ -1 +1 @@ -

Coverage Report

Created: 2025-08-29 11:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\crypto.cpp
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
}
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\crypto.cpp
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
}
\ No newline at end of file diff --git a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/http_client.cpp.html b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/http_client.cpp.html index 81ff79a..b9bc07d 100644 --- a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/http_client.cpp.html +++ b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/http_client.cpp.html @@ -1 +1 @@ -

Coverage Report

Created: 2025-08-29 11:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\http_client.cpp
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
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\http_client.cpp
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
\ No newline at end of file diff --git a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/json_parser.cpp.html b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/json_parser.cpp.html index 7a18877..f70b333 100644 --- a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/json_parser.cpp.html +++ b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/json_parser.cpp.html @@ -1 +1 @@ -

Coverage Report

Created: 2025-08-29 11:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\json_parser.cpp
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
}
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\json_parser.cpp
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
}
\ No newline at end of file diff --git a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/privatebinapi.cpp.html b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/privatebinapi.cpp.html index 1fb22fb..7b38206 100644 --- a/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/privatebinapi.cpp.html +++ b/build-clang/coverage/html/coverage/Users/mbusc/source/repos/privatebin-cpp/src/privatebinapi.cpp.html @@ -1 +1 @@ -

Coverage Report

Created: 2025-08-29 11:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\privatebinapi.cpp
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"
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
C:\Users\mbusc\source\repos\privatebin-cpp\src\privatebinapi.cpp
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"
\ No newline at end of file diff --git a/build-clang/coverage/html/index.html b/build-clang/coverage/html/index.html index a2e6b1a..2351a5d 100644 --- a/build-clang/coverage/html/index.html +++ b/build-clang/coverage/html/index.html @@ -1 +1 @@ -

Coverage Report

Created: 2025-08-29 11:28

Click here for information about interpreting this report.

FilenameFunction CoverageLine CoverageRegion CoverageBranch 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)
Generated by llvm-cov -- llvm version 21.1.0
\ No newline at end of file +

Coverage Report

Created: 2025-08-29 12:37

Click here for information about interpreting this report.

FilenameFunction CoverageLine CoverageRegion CoverageBranch 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)
Generated by llvm-cov -- llvm version 21.1.0
\ No newline at end of file diff --git a/build-clang/coverage/m.profraw b/build-clang/coverage/m.profraw index 5970642..c984f3c 100644 Binary files a/build-clang/coverage/m.profraw and b/build-clang/coverage/m.profraw differ diff --git a/build-clang/coverage/merged.profdata b/build-clang/coverage/merged.profdata index 93718cc..5d3dc2f 100644 Binary files a/build-clang/coverage/merged.profdata and b/build-clang/coverage/merged.profdata differ diff --git a/build-clang/coverage_llvm.vcxproj b/build-clang/coverage_llvm.vcxproj index fdb7073..a795b58 100644 --- a/build-clang/coverage_llvm.vcxproj +++ b/build-clang/coverage_llvm.vcxproj @@ -116,11 +116,11 @@ if %errorlevel% neq 0 goto :cmEnd if %errorlevel% neq 0 goto :cmEnd "C:\Program Files\CMake\bin\cmake.exe" -E make_directory C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata +"C:\Program Files\LLVM\bin\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Debug/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) +"C:\Program Files\LLVM\bin\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Debug/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Debug/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html +"C:\Program Files\LLVM\bin\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Debug/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html if %errorlevel% neq 0 goto :cmEnd :cmEnd endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone @@ -142,11 +142,11 @@ if %errorlevel% neq 0 goto :cmEnd if %errorlevel% neq 0 goto :cmEnd "C:\Program Files\CMake\bin\cmake.exe" -E make_directory C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata +"C:\Program Files\LLVM\bin\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) +"C:\Program Files\LLVM\bin\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html +"C:\Program Files\LLVM\bin\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html if %errorlevel% neq 0 goto :cmEnd :cmEnd endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone @@ -168,11 +168,11 @@ if %errorlevel% neq 0 goto :cmEnd if %errorlevel% neq 0 goto :cmEnd "C:\Program Files\CMake\bin\cmake.exe" -E make_directory C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata +"C:\Program Files\LLVM\bin\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/MinSizeRel/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) +"C:\Program Files\LLVM\bin\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/MinSizeRel/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/MinSizeRel/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html +"C:\Program Files\LLVM\bin\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/MinSizeRel/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html if %errorlevel% neq 0 goto :cmEnd :cmEnd endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone @@ -194,11 +194,11 @@ if %errorlevel% neq 0 goto :cmEnd if %errorlevel% neq 0 goto :cmEnd "C:\Program Files\CMake\bin\cmake.exe" -E make_directory C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata +"C:\Program Files\LLVM\bin\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/RelWithDebInfo/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) +"C:\Program Files\LLVM\bin\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/RelWithDebInfo/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/RelWithDebInfo/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html +"C:\Program Files\LLVM\bin\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/RelWithDebInfo/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html if %errorlevel% neq 0 goto :cmEnd :cmEnd endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone diff --git a/build-clang/tests/CTestTestfile.cmake b/build-clang/tests/CTestTestfile.cmake index e8712c6..6a5d44b 100644 --- a/build-clang/tests/CTestTestfile.cmake +++ b/build-clang/tests/CTestTestfile.cmake @@ -21,16 +21,16 @@ else() endif() if(CTEST_CONFIGURATION_TYPE MATCHES "^([Dd][Ee][Bb][Uu][Gg])$") add_test(example_run "C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/example/Debug/example.exe") - set_tests_properties(example_run PROPERTIES DISABLED "FALSE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") + set_tests_properties(example_run PROPERTIES DISABLED "TRUE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") elseif(CTEST_CONFIGURATION_TYPE MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") add_test(example_run "C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/example/Release/example.exe") - set_tests_properties(example_run PROPERTIES DISABLED "FALSE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") + set_tests_properties(example_run PROPERTIES DISABLED "TRUE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") elseif(CTEST_CONFIGURATION_TYPE MATCHES "^([Mm][Ii][Nn][Ss][Ii][Zz][Ee][Rr][Ee][Ll])$") add_test(example_run "C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/example/MinSizeRel/example.exe") - set_tests_properties(example_run PROPERTIES DISABLED "FALSE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") + set_tests_properties(example_run PROPERTIES DISABLED "TRUE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") elseif(CTEST_CONFIGURATION_TYPE MATCHES "^([Rr][Ee][Ll][Ww][Ii][Tt][Hh][Dd][Ee][Bb][Ii][Nn][Ff][Oo])$") add_test(example_run "C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/example/RelWithDebInfo/example.exe") - set_tests_properties(example_run PROPERTIES DISABLED "FALSE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") + set_tests_properties(example_run PROPERTIES DISABLED "TRUE" _BACKTRACE_TRIPLES "C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;44;add_test;C:/Users/mbusc/source/repos/privatebin-cpp/tests/CMakeLists.txt;0;") else() add_test(example_run NOT_AVAILABLE) endif() diff --git a/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/CustomBuild.command.1.tlog b/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/CustomBuild.command.1.tlog index c7bad63..ceeaabe 100644 --- a/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/CustomBuild.command.1.tlog +++ b/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/CustomBuild.command.1.tlog @@ -8,11 +8,11 @@ if %errorlevel% neq 0 goto :cmEnd if %errorlevel% neq 0 goto :cmEnd "C:\Program Files\CMake\bin\cmake.exe" -E make_directory C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata +"C:\Program Files\LLVM\bin\llvm-profdata.exe" merge -sparse C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/*.profraw -o C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) +"C:\Program Files\LLVM\bin\llvm-cov.exe" report C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) if %errorlevel% neq 0 goto :cmEnd -"C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html +"C:\Program Files\LLVM\bin\llvm-cov.exe" show C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/Release/privatebinapi.dll --instr-profile=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/merged.profdata --ignore-filename-regex= (vcpkg^|external^|CMakeFiles) --format=html --output-dir=C:/Users/mbusc/source/repos/privatebin-cpp/build-clang/coverage/html if %errorlevel% neq 0 goto :cmEnd :cmEnd endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone diff --git a/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/unsuccessfulbuild b/build-clang/x64/Release/coverage_llvm/coverage_llvm.tlog/unsuccessfulbuild deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/create_release.ps1 b/scripts/create_release.ps1 index 7b285f9..ee47729 100644 --- a/scripts/create_release.ps1 +++ b/scripts/create_release.ps1 @@ -78,13 +78,15 @@ if (Test-Path "build\CTestTestfile.cmake") { # Optional: clang-cl Coverage bauen und HTML-Report als Artefakt paketieren Write-Host "Erzeuge optionalen Coverage-Report (clang-cl + LLVM), falls Tools vorhanden sind..." -ForegroundColor Yellow try { - $llvmCov = "C:\\Program Files\\LLVM\\bin\\llvm-cov.exe" - $llvmProf = "C:\\Program Files\\LLVM\\bin\\llvm-profdata.exe" + $llvmCov = "C:\Program Files\LLVM\bin\llvm-cov.exe" + $llvmProf = "C:\Program Files\LLVM\bin\llvm-profdata.exe" $llvmCovExists = Test-Path $llvmCov $llvmProfExists = Test-Path $llvmProf + if ($llvmCovExists -and $llvmProfExists) { $toolchain = Join-Path $env:USERPROFILE 'vcpkg\scripts\buildsystems\vcpkg.cmake' $haveToolchain = Test-Path $toolchain + $cmakeArgs = @( '-S','.', '-B','build-clang', @@ -95,23 +97,33 @@ try { ("-DLLVM_PROFDATA={0}" -f $llvmProf), ("-DLLVM_COV={0}" -f $llvmCov) ) + if ($haveToolchain) { $cmakeArgs += ("-DCMAKE_TOOLCHAIN_FILE={0}" -f $toolchain) $cmakeArgs += '-DVCPKG_TARGET_TRIPLET=x64-windows' } + & cmake @cmakeArgs | Out-Null - if ($LASTEXITCODE -ne 0) { throw "CMake-Konfiguration für Coverage fehlgeschlagen" } + if ($LASTEXITCODE -ne 0) { + throw "CMake-Konfiguration für Coverage fehlgeschlagen" + } # Run coverage target with integration tests disabled to avoid rate limits $env:PRIVATEBIN_IT = '0' & cmake --build build-clang --config Release --target coverage_llvm - if ($LASTEXITCODE -ne 0) { throw "Coverage-Build fehlgeschlagen" } + if ($LASTEXITCODE -ne 0) { + throw "Coverage-Build fehlgeschlagen" + } - $htmlDir = "build-clang/coverage/html" + $htmlDir = "build-clang\coverage\html" if (Test-Path $htmlDir) { - if (-not (Test-Path 'dist')) { New-Item -ItemType Directory -Path 'dist' | Out-Null } - $zipPath = "dist/coverage_html.zip" - if (Test-Path $zipPath) { Remove-Item -Force $zipPath } + if (-not (Test-Path 'dist')) { + New-Item -ItemType Directory -Path 'dist' | Out-Null + } + $zipPath = "dist\coverage_html.zip" + if (Test-Path $zipPath) { + Remove-Item -Force $zipPath + } Compress-Archive -Path "$htmlDir\*" -DestinationPath $zipPath Write-Host "Coverage-HTML nach $zipPath gepackt." -ForegroundColor Green } else { @@ -124,15 +136,16 @@ try { Write-Host "Warnung: Coverage-Erzeugung fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Yellow } -<# - Commit & Push, Tag & Push -#> +# Commit & Push, Tag & Push # Änderungen committen $status = git status --porcelain if ($status) { git add -A git commit -m "Release $newVersion prepare for release" - if ($LASTEXITCODE -ne 0) { Write-Host "Commit fehlgeschlagen." -ForegroundColor Red; exit 1 } + if ($LASTEXITCODE -ne 0) { + Write-Host "Commit fehlgeschlagen." -ForegroundColor Red + exit 1 + } } # Remote/Repo-Infos ermitteln @@ -143,7 +156,10 @@ if (-not $remoteUrl) { } # Host, Owner, Repo aus URL extrahieren (https oder ssh) -$gitHost = $null; $owner = $null; $repoName = $null +$gitHost = $null +$owner = $null +$repoName = $null + if ($remoteUrl -match '^(https?://)([^/]+)/(.+?)(?:\.git)?$') { $gitHost = $matches[2] $path = $matches[3] @@ -151,6 +167,7 @@ if ($remoteUrl -match '^(https?://)([^/]+)/(.+?)(?:\.git)?$') { $gitHost = $matches[1] $path = $matches[2] } + if ($path) { $parts = $path.Split('/') if ($parts.Length -ge 2) { @@ -158,6 +175,7 @@ if ($path) { $repoName = $parts[$parts.Length-1] } } + if (-not $gitHost -or -not $owner -or -not $repoName) { Write-Host "Fehler: Konnte Host/Owner/Repo aus Remote-URL nicht ermitteln: $remoteUrl" -ForegroundColor Red exit 1 @@ -165,7 +183,11 @@ if (-not $gitHost -or -not $owner -or -not $repoName) { # Tag erstellen und pushen git tag -a $newVersion -m "Release $newVersion" -if ($LASTEXITCODE -ne 0) { Write-Host "Tag-Erstellung fehlgeschlagen." -ForegroundColor Red; exit 1 } +if ($LASTEXITCODE -ne 0) { + Write-Host "Tag-Erstellung fehlgeschlagen." -ForegroundColor Red + exit 1 +} + git push origin $newVersion if ($LASTEXITCODE -ne 0) { Write-Host "Tag-Push fehlgeschlagen. Stelle sicher, dass Du Push-Rechte besitzt." -ForegroundColor Red @@ -195,6 +217,7 @@ $headers = @{ } $releaseUri = "https://$gitHost/api/v1/repos/$owner/$repoName/releases" + try { $release = Invoke-RestMethod -Uri $releaseUri -Method Post -Headers $headers -Body $releaseData -ErrorAction Stop } catch { @@ -259,5 +282,4 @@ if (Test-Path $distPath) { Write-Host "dist-Ordner nicht gefunden!" -ForegroundColor Yellow } -Write-Host "Release-Erstellung abgeschlossen!" -ForegroundColor Green - +Write-Host "Release-Erstellung abgeschlossen!" -ForegroundColor Green \ No newline at end of file diff --git a/scripts/create_release_simple.ps1 b/scripts/create_release_simple.ps1 new file mode 100644 index 0000000..46b4739 --- /dev/null +++ b/scripts/create_release_simple.ps1 @@ -0,0 +1,284 @@ +param( + [Parameter(Mandatory=$false)] + [string]$Token +) + +# Token aus Umgebungsvariable laden falls nicht als Parameter übergeben +if (-not $Token) { + $Token = $env:GITEA_TOKEN + if (-not $Token) { + Write-Host "Fehler: Kein Token angegeben und GITEA_TOKEN Umgebungsvariable nicht gesetzt!" -ForegroundColor Red + Write-Host "Verwendung: .\create_release_simple.ps1 -Token 'your_token' oder setze GITEA_TOKEN Umgebungsvariable" -ForegroundColor Yellow + Write-Host "Das Script führt automatisch einen Build durch und lädt alle Artefakte hoch." -ForegroundColor Cyan + exit 1 + } + Write-Host "Token aus Umgebungsvariable GITEA_TOKEN geladen" -ForegroundColor Green +} + +Write-Host "=== lib-privatebin Release Creator ===" -ForegroundColor Cyan + +# Aktuelle Version ermitteln +$lastTag = git describe --tags --abbrev=0 2>$null +if (-not $lastTag) { + $lastTag = "v0.1.0" +} + +Write-Host "Letzter Tag: $lastTag" -ForegroundColor Green + +# Version parsen +if ($lastTag -match "^v(\d+)\.(\d+)\.(\d+)(.*)$") { + $major = [int]$matches[1] + $minor = [int]$matches[2] + $patch = [int]$matches[3] + $suffix = $matches[4] + + $newPatch = $patch + 1 + $newVersion = "v$major.$minor.$newPatch$suffix" + + Write-Host "Neue Version: $newVersion" -ForegroundColor Green +} else { + Write-Host "Fehler: Ungültiges Versionsformat: $lastTag" -ForegroundColor Red + exit 1 +} + +# Build durchführen +Write-Host "Führe Build durch..." -ForegroundColor Yellow +if (Test-Path "scripts\build_windows.bat") { + cmd /c scripts\build_windows.bat + if ($LASTEXITCODE -ne 0) { + Write-Host "Build fehlgeschlagen!" -ForegroundColor Red + exit 1 + } +} else { + Write-Host "Fehler: scripts\build_windows.bat nicht gefunden!" -ForegroundColor Red + exit 1 +} + +Write-Host "Build erfolgreich abgeschlossen!" -ForegroundColor Green + +# Tests ausführen +Write-Host "Führe Tests aus..." -ForegroundColor Yellow +if (Test-Path "build\CTestTestfile.cmake") { + pushd build + try { + ctest -C Release --output-on-failure + if ($LASTEXITCODE -ne 0) { + Write-Host "Tests fehlgeschlagen!" -ForegroundColor Red + exit 1 + } + } finally { + popd + } + Write-Host "Tests erfolgreich." -ForegroundColor Green +} else { + Write-Host "Warnung: Keine Tests gefunden." -ForegroundColor Yellow +} + +# Optional: clang-cl Coverage bauen und HTML-Report als Artefakt paketieren +Write-Host "Erzeuge optionalen Coverage-Report (clang-cl + LLVM), falls Tools vorhanden sind..." -ForegroundColor Yellow +try { + $llvmCov = "C:\Program Files\LLVM\bin\llvm-cov.exe" + $llvmProf = "C:\Program Files\LLVM\bin\llvm-profdata.exe" + $llvmCovExists = Test-Path $llvmCov + $llvmProfExists = Test-Path $llvmProf + + if ($llvmCovExists -and $llvmProfExists) { + $toolchain = Join-Path $env:USERPROFILE 'vcpkg\scripts\buildsystems\vcpkg.cmake' + $haveToolchain = Test-Path $toolchain + + $cmakeArgs = @( + '-S','.', + '-B','build-clang', + '-G','Visual Studio 17 2022', + '-A','x64', + '-T','ClangCL', + '-DENABLE_LLVM_COVERAGE=ON', + ("-DLLVM_PROFDATA={0}" -f $llvmProf), + ("-DLLVM_COV={0}" -f $llvmCov) + ) + + if ($haveToolchain) { + $cmakeArgs += ("-DCMAKE_TOOLCHAIN_FILE={0}" -f $toolchain) + $cmakeArgs += '-DVCPKG_TARGET_TRIPLET=x64-windows' + } + + & cmake @cmakeArgs | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "CMake-Konfiguration für Coverage fehlgeschlagen" + } + + # Run coverage target with integration tests disabled to avoid rate limits + $env:PRIVATEBIN_IT = '0' + & cmake --build build-clang --config Release --target coverage_llvm + if ($LASTEXITCODE -ne 0) { + throw "Coverage-Build fehlgeschlagen" + } + + $htmlDir = "build-clang\coverage\html" + if (Test-Path $htmlDir) { + if (-not (Test-Path 'dist')) { + New-Item -ItemType Directory -Path 'dist' | Out-Null + } + $zipPath = "dist\coverage_html.zip" + if (Test-Path $zipPath) { + Remove-Item -Force $zipPath + } + Compress-Archive -Path "$htmlDir\*" -DestinationPath $zipPath + Write-Host "Coverage-HTML nach $zipPath gepackt." -ForegroundColor Green + } else { + Write-Host "Hinweis: Kein Coverage-HTML-Verzeichnis gefunden (Tests evtl. ohne Ausführungspfad)." -ForegroundColor Yellow + } + } else { + Write-Host "LLVM-Tools nicht gefunden; überspringe Coverage (erwartet: $llvmCov / $llvmProf)." -ForegroundColor Yellow + } +} catch { + Write-Host "Warnung: Coverage-Erzeugung fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Yellow +} + +# Commit & Push, Tag & Push +# Änderungen committen +$status = git status --porcelain +if ($status) { + git add -A + git commit -m "Release $newVersion prepare for release" + if ($LASTEXITCODE -ne 0) { + Write-Host "Commit fehlgeschlagen." -ForegroundColor Red + exit 1 + } +} + +# Remote/Repo-Infos ermitteln +$remoteUrl = git remote get-url origin 2>$null +if (-not $remoteUrl) { + Write-Host "Fehler: Kein Git-Remote 'origin' gefunden." -ForegroundColor Red + exit 1 +} + +# Host, Owner, Repo aus URL extrahieren (https oder ssh) +$gitHost = $null +$owner = $null +$repoName = $null + +if ($remoteUrl -match '^(https?://)([^/]+)/(.+?)(?:\.git)?$') { + $gitHost = $matches[2] + $path = $matches[3] +} elseif ($remoteUrl -match '^[^@]+@([^:]+):(.+?)(?:\.git)?$') { + $gitHost = $matches[1] + $path = $matches[2] +} + +if ($path) { + $parts = $path.Split('/') + if ($parts.Length -ge 2) { + $owner = $parts[$parts.Length-2] + $repoName = $parts[$parts.Length-1] + } +} + +if (-not $gitHost -or -not $owner -or -not $repoName) { + Write-Host "Fehler: Konnte Host/Owner/Repo aus Remote-URL nicht ermitteln: $remoteUrl" -ForegroundColor Red + exit 1 +} + +# Tag erstellen und pushen +git tag -a $newVersion -m "Release $newVersion" +if ($LASTEXITCODE -ne 0) { + Write-Host "Tag-Erstellung fehlgeschlagen." -ForegroundColor Red + exit 1 +} + +git push origin $newVersion +if ($LASTEXITCODE -ne 0) { + Write-Host "Tag-Push fehlgeschlagen. Stelle sicher, dass Du Push-Rechte besitzt." -ForegroundColor Red + exit 1 +} + +# Vor Upload: Artefakte einsammeln (dist) +if (Test-Path "scripts\collect_binaries.ps1") { + Write-Host "Sammle Build-Artefakte (scripts/collect_binaries.ps1)..." -ForegroundColor Yellow + powershell -NoProfile -ExecutionPolicy Bypass -File scripts\collect_binaries.ps1 +} + +# Release erstellen +$releaseBody = "## What is New in $newVersion`n`n- AUTOMATED: Release created by script`n- VERSION: Bumped from $lastTag to $newVersion`n- BUILD: Automatic build with build_windows.bat`n`n## Build Artifacts`n`nBuild-Artefakte werden automatisch hochgeladen und hier angezeigt.`n`nTypische Artefakte:`n- privatebinapi.dll - Windows Dynamic Link Library`n- privatebinapi.lib - Windows Import Library`n- example.exe - Combined example program`n- privatebinapi.h - C++ header file" + +$releaseData = @{ + tag_name = $newVersion + name = "$newVersion - Automated Release" + body = $releaseBody + draft = $false + prerelease = $false +} | ConvertTo-Json -Depth 10 + +$headers = @{ + "Authorization" = "token $Token" + "Content-Type" = "application/json" +} + +$releaseUri = "https://$gitHost/api/v1/repos/$owner/$repoName/releases" + +try { + $release = Invoke-RestMethod -Uri $releaseUri -Method Post -Headers $headers -Body $releaseData -ErrorAction Stop +} catch { + Write-Host "Fehler beim Erstellen des Releases: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} + +Write-Host "Release erstellt: $($release.id)" -ForegroundColor Green +Write-Host "URL: $($release.html_url)" -ForegroundColor Green + +# Build-Artefakte zum Release hinzufügen +Write-Host "Füge Build-Artefakte hinzu..." -ForegroundColor Yellow + +# Artefakte aus dem dist-Ordner finden +$distPath = "dist" +if (Test-Path $distPath) { + $artifacts = Get-ChildItem -Path $distPath -Recurse -File | Where-Object { + $_.Extension -match "\.(dll|lib|exe|h|zip)$" + } + + if ($artifacts) { + Write-Host "Gefundene Artefakte:" -ForegroundColor Green + foreach ($artifact in $artifacts) { + Write-Host " - $($artifact.Name)" -ForegroundColor White + } + + # Artefakte hochladen + foreach ($artifact in $artifacts) { + Write-Host "Lade hoch: $($artifact.Name)..." -ForegroundColor Yellow + + $uploadUri = "https://$gitHost/api/v1/repos/$owner/$repoName/releases/$($release.id)/assets" + + $boundary = [System.Guid]::NewGuid().ToString() + $LF = "`r`n" + $bodyLines = @( + "--$boundary", + "Content-Disposition: form-data; name=`"attachment`"; filename=`"$($artifact.Name)`"", + "Content-Type: application/octet-stream", + "", + [System.IO.File]::ReadAllBytes($artifact.FullName), + "--$boundary--" + ) + + $body = $bodyLines -join $LF + + $uploadHeaders = @{ + "Authorization" = "token $Token" + "Content-Type" = "multipart/form-data; boundary=$boundary" + } + + try { + $uploadResponse = Invoke-RestMethod -Uri $uploadUri -Method Post -Headers $uploadHeaders -Body $body + Write-Host " ✓ $($artifact.Name) erfolgreich hochgeladen" -ForegroundColor Green + } catch { + Write-Host " ✗ Fehler beim Hochladen von $($artifact.Name): $($_.Exception.Message)" -ForegroundColor Red + } + } + } else { + Write-Host "Keine Build-Artefakte im dist-Ordner gefunden!" -ForegroundColor Yellow + } +} else { + Write-Host "dist-Ordner nicht gefunden!" -ForegroundColor Yellow +} + +Write-Host "Release-Erstellung erfolgreich abgeschlossen!" -ForegroundColor Green