Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77879e6521 | |||
| 3b4d591eff | |||
| 4add1edd11 | |||
| 5493c0dcf3 | |||
| cd7e957692 | |||
| d712d3a9d8 | |||
| 000fde485f | |||
| b97d9f2d7f | |||
| 9b77d041dc | |||
| b30a36b884 | |||
| 3c2c2b35e2 | |||
| 5528096614 | |||
| 0b4b5244f1 | |||
| eecbc47f5f | |||
| a741d3b969 | |||
| d04fae8bbd | |||
| 48eec02cca | |||
| 8c4926cbae | |||
| bafe712020 | |||
| 063800df12 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
build/
|
||||
cmake-build-*/
|
||||
|
||||
dist/
|
||||
|
||||
# Compiled object files
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
1
.prompts.txt
Normal file
1
.prompts.txt
Normal file
@ -0,0 +1 @@
|
||||
erstelle das packaging und füge die assets dem letzten release auf gitea hinzu. Gitea Token: 3017537155e35026e9cf94e0fd50fb66f285777f
|
||||
70
CHANGELOG.md
Normal file
70
CHANGELOG.md
Normal file
@ -0,0 +1,70 @@
|
||||
## v0.1.1.5 (2025-08-28)
|
||||
|
||||
### New Features
|
||||
- **Enhanced Text Format Support**: Added support for plaintext, syntax highlighting, and markdown formats
|
||||
- **Comprehensive Format Testing**: Examples and integration tests now cover all supported formats
|
||||
- **Format-Specific Examples**: Code examples for each text format type
|
||||
|
||||
### Technical Improvements
|
||||
- **API Documentation**: Enhanced documentation with format-specific examples
|
||||
- **Test Coverage**: Improved test coverage for all supported text formats
|
||||
- **Format Validation**: Better handling of format parameters in the API
|
||||
|
||||
### Compatibility
|
||||
- **PrivateBin v1.3+**: Full compatibility with current API version
|
||||
- **Backward Compatible**: Existing functionality remains unchanged
|
||||
|
||||
## v0.1.1.4 (2025-08-28)
|
||||
- **NEW**: Automated release creation script (`scripts/create_release.ps1`)
|
||||
- **NEW**: Build scripts moved to `scripts/` directory for better organization
|
||||
- **IMPROVED**: Enhanced build documentation with platform-specific instructions
|
||||
- **IMPROVED**: Better project structure and organization
|
||||
|
||||
## v0.1.1.3 (2025-08-28)
|
||||
|
||||
### New Features
|
||||
- **File Upload Functionality**: New `upload_file()` function added
|
||||
- **Binary Files**: Support for uploading arbitrary file types
|
||||
- **Enhanced Security**: Same end-to-end encryption as text pastes
|
||||
- **File Upload Example**: New example program `file_upload_example` demonstrates the functionality
|
||||
|
||||
### Technical Improvements
|
||||
- **File Processing**: Binary files are correctly read and processed
|
||||
- **Size Limitation**: Maximum file size limited to 100MB
|
||||
- **Compression**: Automatic zlib compression before encryption
|
||||
- **Metadata**: Files are stored as encrypted binary pastes
|
||||
|
||||
### Documentation
|
||||
- **FILE_UPLOAD_README.md**: Detailed documentation of file upload functionality
|
||||
- **README.md**: Updated with file upload information and examples
|
||||
- **English Localization**: All documentation and examples in English
|
||||
|
||||
### Compatibility
|
||||
- **PrivateBin v1.3+**: Full compatibility with current API version
|
||||
- **Cross-Platform**: Support for Windows and Linux
|
||||
- **Backward Compatible**: Existing text paste functionality remains unchanged
|
||||
|
||||
## v0.1.1.2 (2024-XX-XX)
|
||||
|
||||
### Bugfixes
|
||||
- Improved error handling for network issues
|
||||
- Fixed memory leaks in JSON processing
|
||||
|
||||
### Improvements
|
||||
- Updated dependencies (Crypto++, nlohmann/json)
|
||||
- Better Windows compatibility
|
||||
|
||||
## v0.1.1.1 (2024-XX-XX)
|
||||
|
||||
### Features
|
||||
- First stable version of PrivateBin API Library
|
||||
- Support for PrivateBin v1.3 JSON-API
|
||||
- End-to-end encryption with AES-256-GCM
|
||||
- Cross-platform support (Windows/Linux)
|
||||
|
||||
### API Functions
|
||||
- `create_paste()` - Create encrypted text pastes
|
||||
- `get_paste()` - Retrieve and decrypt pastes
|
||||
- `delete_paste()` - Delete pastes with deletion tokens
|
||||
- `free_string()` - Memory management
|
||||
|
||||
@ -17,8 +17,16 @@ elseif(UNIX)
|
||||
endif()
|
||||
|
||||
# Handle dependencies
|
||||
find_package(cryptopp CONFIG REQUIRED)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
if(WIN32)
|
||||
# Windows: Use vcpkg packages
|
||||
find_package(cryptopp CONFIG REQUIRED)
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
else()
|
||||
# Linux: Use system packages
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(CRYPTOPP REQUIRED libcryptopp)
|
||||
pkg_check_modules(NLOHMANN_JSON REQUIRED nlohmann_json)
|
||||
endif()
|
||||
|
||||
# Add library sources
|
||||
set(SOURCES
|
||||
@ -40,22 +48,42 @@ set(HEADERS
|
||||
# Create the shared library
|
||||
add_library(privatebinapi SHARED ${SOURCES} ${HEADERS})
|
||||
|
||||
# Define PRIVATEBINAPI_EXPORTS for the library build
|
||||
target_compile_definitions(privatebinapi PRIVATE PRIVATEBINAPI_EXPORTS)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(privatebinapi PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
# Explicitly include vcpkg directories
|
||||
target_include_directories(privatebinapi PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/x64-windows/include
|
||||
)
|
||||
# Platform-specific include directories
|
||||
if(WIN32)
|
||||
# Windows: Include vcpkg directories
|
||||
target_include_directories(privatebinapi PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/x64-windows/include
|
||||
)
|
||||
endif()
|
||||
|
||||
# Link dependencies
|
||||
target_link_libraries(privatebinapi PRIVATE
|
||||
cryptopp::cryptopp
|
||||
nlohmann_json::nlohmann_json
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
if(WIN32)
|
||||
# Windows: Use vcpkg targets
|
||||
target_link_libraries(privatebinapi PRIVATE
|
||||
cryptopp::cryptopp
|
||||
nlohmann_json::nlohmann_json
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
else()
|
||||
# Linux: Use system libraries
|
||||
target_link_libraries(privatebinapi PRIVATE
|
||||
${CRYPTOPP_LIBRARIES}
|
||||
${NLOHMANN_JSON_LIBRARIES}
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
target_include_directories(privatebinapi PRIVATE
|
||||
${CRYPTOPP_INCLUDE_DIRS}
|
||||
${NLOHMANN_JSON_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install targets
|
||||
install(TARGETS privatebinapi
|
||||
@ -84,12 +112,11 @@ if(ENABLE_LLVM_COVERAGE)
|
||||
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
|
||||
add_link_options(-fprofile-instr-generate)
|
||||
|
||||
# Helper variables for report tools (can be overridden from environment)
|
||||
# Helper variables for report tools
|
||||
set(LLVM_PROFDATA "llvm-profdata" CACHE STRING "Path to llvm-profdata")
|
||||
set(LLVM_COV "llvm-cov" CACHE STRING "Path to llvm-cov")
|
||||
|
||||
# Custom target to run tests and produce coverage report
|
||||
# Usage: cmake --build build --target coverage_llvm --config Release
|
||||
add_custom_target(
|
||||
coverage_llvm
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
@ -110,4 +137,4 @@ if(ENABLE_LLVM_COVERAGE)
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS privatebinapi
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
632
README.md
632
README.md
@ -1,227 +1,497 @@
|
||||
# PrivateBin API C++ DLL
|
||||
# PrivateBin API Library
|
||||
|
||||
A cross-platform C++ library for interacting with PrivateBin servers.
|
||||
|
||||
## Overview
|
||||
|
||||
This library provides a simple C++ interface for interacting with PrivateBin services. PrivateBin is a minimalist, open-source online pastebin where the server has zero knowledge of stored data. All data is encrypted and decrypted in the browser using 256-bit AES encryption.
|
||||
A C++ library for interacting with PrivateBin servers via the JSON API v1.3.
|
||||
|
||||
## Features
|
||||
|
||||
- Create new pastes with optional expiration, formatting, and security settings
|
||||
- Retrieve existing pastes by ID
|
||||
- Delete pastes using the deletion token
|
||||
- Cross-platform compatibility (Windows and Linux)
|
||||
- Support for PrivateBin API versions 1.3 and later
|
||||
- **Text Paste Creation**: Create encrypted text pastes with multiple formats
|
||||
- **File Upload**: Upload files as encrypted pastes
|
||||
- **Paste Retrieval**: Retrieve and decrypt pastes
|
||||
- **Paste Deletion**: Delete pastes with deletion tokens
|
||||
- **End-to-End Encryption**: Client-side encryption with AES-256-GCM
|
||||
- **Cross-Platform**: Support for Windows and Linux
|
||||
- **Complete API**: Implementation of all PrivateBin v1.3 API functions
|
||||
- **Automated Builds**: Platform-specific build scripts for easy compilation
|
||||
- **Text Formatting**: Support for plaintext, syntax highlighting, and markdown
|
||||
|
||||
## Development & Build
|
||||
## Text Formatting Support
|
||||
|
||||
### Prerequisites
|
||||
The library supports multiple text formats as defined in the [PrivateBin API v1.3](https://github.com/PrivateBin/PrivateBin/wiki/API):
|
||||
|
||||
- CMake 3.10+
|
||||
- C++17-capable compiler (MSVC 2022 or GCC/Clang)
|
||||
- Git
|
||||
- vcpkg (automatically bootstrapped by the Makefile)
|
||||
### Available Formats
|
||||
|
||||
- **`plaintext`** - Plain text (default format)
|
||||
- **`syntaxhighlighting`** - Syntax highlighting for code snippets
|
||||
- **`markdown`** - Markdown formatting for rich text
|
||||
|
||||
### Format Usage
|
||||
|
||||
```c
|
||||
#include "privatebinapi.h"
|
||||
|
||||
// Plain text format
|
||||
create_paste(server, content, password, expiration, "plaintext", 0, 0, &url, &token);
|
||||
|
||||
// Syntax highlighting for code
|
||||
create_paste(server, code_content, password, expiration, "syntaxhighlighting", 0, 0, &url, &token);
|
||||
|
||||
// Markdown formatting
|
||||
create_paste(server, markdown_content, password, expiration, "markdown", 0, 0, &url, &token);
|
||||
```
|
||||
|
||||
## File Upload Functionality
|
||||
|
||||
The library includes an `upload_file` function that allows you to securely upload files to PrivateBin servers:
|
||||
|
||||
```c
|
||||
int upload_file(const char* server_url, const char* file_path,
|
||||
const char* password, const char* expiration,
|
||||
int burn_after_reading, int open_discussion,
|
||||
char** paste_url, char** delete_token);
|
||||
```
|
||||
|
||||
### File Upload Parameters
|
||||
|
||||
- **`server_url`**: The URL of the PrivateBin server
|
||||
- **`file_path`**: The path to the file to upload
|
||||
- **`password`**: Optional password for the paste (can be NULL)
|
||||
- **`expiration`**: Expiration time ("5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never")
|
||||
- **`burn_after_reading`**: 1 for "burn after reading", 0 for "keep"
|
||||
- **`open_discussion`**: 1 for allow discussion, 0 for disable discussion
|
||||
- **`paste_url`**: Output parameter for the URL of the created paste
|
||||
- **`delete_token`**: Output parameter for the deletion token
|
||||
|
||||
### File Upload Features
|
||||
|
||||
- **Binary Files**: Support for all file types
|
||||
- **Size Limitation**: Maximum file size 100MB
|
||||
- **Secure Encryption**: Same cryptography as text pastes
|
||||
- **Compression**: Automatic zlib compression before encryption
|
||||
- **Metadata**: File name, size, and type are added to metadata
|
||||
- **Key Derivation**: PBKDF2-HMAC-SHA256 with 100,000 iterations
|
||||
|
||||
### How File Upload Works
|
||||
|
||||
1. **File Reading**: The file is read in binary mode
|
||||
2. **Size Check**: Maximum file size is limited to 100MB
|
||||
3. **Encryption**:
|
||||
- Generation of a random 32-byte key
|
||||
- File compression with zlib
|
||||
- Encryption with AES-256-GCM
|
||||
- Key derivation with PBKDF2-HMAC-SHA256 (100,000 iterations)
|
||||
4. **Metadata**: File name, size, and type are added to metadata
|
||||
5. **Upload**: Encrypted data is sent to the PrivateBin server
|
||||
6. **URL Generation**: The URL is created with the Base58-encoded key
|
||||
|
||||
## Build Scripts
|
||||
|
||||
The project includes several build scripts in the `scripts/` directory for different platforms and use cases.
|
||||
|
||||
### Windows Build Scripts
|
||||
|
||||
#### PowerShell Build (Recommended)
|
||||
**File:** `scripts/build_windows.ps1`
|
||||
**Requirements:** PowerShell, Visual Studio 2022 Build Tools (C++), vcpkg (will be bootstrapped), Git
|
||||
|
||||
```powershell
|
||||
# Run from project root
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build_windows.ps1
|
||||
```
|
||||
|
||||
This script:
|
||||
- Automatically bootstraps vcpkg if not present
|
||||
- Configures CMake with Visual Studio 2022 generator
|
||||
- Builds the project in Release configuration
|
||||
- Creates a Windows distribution package with all artifacts
|
||||
|
||||
#### Command Line Build
|
||||
**File:** `scripts/build_thinkpad.bat`
|
||||
**Requirements:** Visual Studio 2022 Build Tools/Community with C++ workload, vcpkg
|
||||
|
||||
```cmd
|
||||
# Run from project root
|
||||
scripts\build_thinkpad.bat
|
||||
```
|
||||
|
||||
This script:
|
||||
- Automatically detects and initializes Visual Studio environment
|
||||
- Handles vcpkg bootstrapping
|
||||
- Configures CMake with proper toolchain
|
||||
- Builds the project using MSVC compiler
|
||||
|
||||
### Linux Build Scripts
|
||||
|
||||
#### Automated Build Script
|
||||
**File:** `scripts/build.sh`
|
||||
**Requirements:** CMake, C++17 compiler, system packages
|
||||
|
||||
```bash
|
||||
# Make executable and run from project root
|
||||
chmod +x scripts/build.sh
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
- Automatically detects and installs required dependencies
|
||||
- Configures CMake with Unix Makefiles
|
||||
- Builds the project in Release configuration
|
||||
|
||||
#### Manual Linux Build (Recommended)
|
||||
**Requirements:** CMake 3.10+, GCC/Clang with C++17 support, system packages
|
||||
|
||||
```bash
|
||||
# Install dependencies (Fedora/RHEL/CentOS)
|
||||
sudo dnf install cmake gcc-c++ libcurl-devel cryptopp-devel json-devel
|
||||
|
||||
# Install dependencies (Ubuntu/Debian)
|
||||
sudo apt install cmake g++ libcurl4-openssl-dev libcrypto++-dev nlohmann-json3-dev
|
||||
|
||||
# Build the project
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
# Run tests
|
||||
ctest --verbose
|
||||
|
||||
# Run example
|
||||
./example/example
|
||||
```
|
||||
|
||||
**Linux Dependencies:**
|
||||
- **libcurl-devel** - HTTP client library
|
||||
- **cryptopp-devel** - Cryptographic library
|
||||
- **json-devel** - JSON parsing library
|
||||
- **cmake** - Build system
|
||||
- **gcc-c++** - C++17 compiler
|
||||
|
||||
### macOS Build Script
|
||||
**File:** `scripts/build.sh`
|
||||
**Requirements:** CMake, C++17 compiler, Homebrew packages
|
||||
|
||||
```bash
|
||||
# Install dependencies via Homebrew
|
||||
brew install cmake cryptopp nlohmann-json curl
|
||||
|
||||
# Make executable and run from project root
|
||||
chmod +x scripts/build.sh
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
- Bootstraps vcpkg if not present
|
||||
- Configures CMake with Unix Makefiles
|
||||
- Builds the project in Release configuration
|
||||
|
||||
## Installation
|
||||
|
||||
### Dependencies
|
||||
|
||||
- cryptopp (Crypto++)
|
||||
- nlohmann-json
|
||||
- Windows: WinHTTP (SDK)
|
||||
- Linux: libcurl (used automatically via vcpkg)
|
||||
- CMake 3.10+
|
||||
- C++17 compatible compiler
|
||||
- **Windows:** Crypto++ and nlohmann/json (via vcpkg)
|
||||
- **Linux:** libcurl, cryptopp, and nlohmann-json (via system packages)
|
||||
- **macOS:** libcurl, cryptopp, and nlohmann/json (via Homebrew)
|
||||
|
||||
### Quick start with Makefile (Windows & Linux)
|
||||
### Compilation
|
||||
|
||||
1) Install dependencies, configure, and build:
|
||||
|
||||
```
|
||||
make
|
||||
#### Windows (PowerShell) - Build via scripts/build_windows.ps1
|
||||
```powershell
|
||||
# Requires: PowerShell, Visual Studio 2022 Build Tools (C++), vcpkg (will be bootstrapped), Git
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build_windows.ps1
|
||||
```
|
||||
|
||||
2) Build and run the example:
|
||||
|
||||
```
|
||||
make example
|
||||
#### Windows (Command Line) - Build via scripts/build_thinkpad.bat
|
||||
```cmd
|
||||
# Requires: Visual Studio 2022 Build Tools/Community with C++ workload, vcpkg
|
||||
scripts\build_thinkpad.bat
|
||||
```
|
||||
|
||||
The Makefile does:
|
||||
- clone and bootstrap vcpkg
|
||||
- install packages from `vcpkg.json`
|
||||
- configure CMake with the vcpkg toolchain
|
||||
- build the library and the example
|
||||
|
||||
### Manual with CMake
|
||||
|
||||
```
|
||||
# clone and bootstrap vcpkg
|
||||
git clone https://github.com/microsoft/vcpkg.git "$HOME/vcpkg"
|
||||
"$HOME/vcpkg/bootstrap-vcpkg.sh" # Linux/macOS
|
||||
# Windows (PowerShell):
|
||||
# powershell -NoProfile -ExecutionPolicy Bypass -Command "& '$env:USERPROFILE\vcpkg\bootstrap-vcpkg.bat'"
|
||||
|
||||
# Configure
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
||||
|
||||
# Build
|
||||
cmake --build build --config Release
|
||||
|
||||
# Example
|
||||
cmake -S example -B example/build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build example/build --config Release
|
||||
#### Linux - Automated Build via scripts/build.sh
|
||||
```bash
|
||||
# Requires: CMake, C++17 compiler, system packages
|
||||
chmod +x scripts/build.sh
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
### Windows (PowerShell) – Build via build_thinkpad.bat
|
||||
#### Linux - Manual Build (Recommended)
|
||||
```bash
|
||||
# Install dependencies (Fedora/RHEL/CentOS)
|
||||
sudo dnf install cmake gcc-c++ libcurl-devel cryptopp-devel json-devel
|
||||
|
||||
For systems with Visual Studio 2022 Build Tools (C++ workload) and vcpkg in the user profile, there is a robust build script:
|
||||
# Install dependencies (Ubuntu/Debian)
|
||||
sudo apt install cmake g++ libcurl4-openssl-dev libcrypto++-dev nlohmann-json3-dev
|
||||
|
||||
```
|
||||
cd C:\Users\mbusc\source\repos\lib-privatebin
|
||||
./build_thinkpad.bat
|
||||
# Build the project
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
# Run tests and examples
|
||||
ctest --verbose
|
||||
./example/example
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Requires Visual Studio 2022 Build Tools with C++ tools and the Windows 11 SDK. If `VsDevCmd.bat` is found, the script automatically initializes the MSVC environment.
|
||||
- vcpkg is bootstrapped if needed; missing dependencies (`cryptopp`, `nlohmann-json`) are installed.
|
||||
- If vcpkg requires a baseline, the script sets it automatically.
|
||||
#### macOS - Build via scripts/build.sh
|
||||
```bash
|
||||
# Install dependencies via Homebrew
|
||||
brew install cmake cryptopp nlohmann-json curl
|
||||
|
||||
Edition/Path notes:
|
||||
- If you use another VS edition (e.g., Professional/Enterprise), adjust the paths in `build_thinkpad.bat`:
|
||||
- `C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\Tools\VsDevCmd.bat`
|
||||
- `C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat`
|
||||
- Alternatively, you can use `vswhere.exe` to discover the installation path:
|
||||
```powershell
|
||||
& 'C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe' -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||
```
|
||||
# Requires: CMake, C++17 compiler, Homebrew packages
|
||||
chmod +x scripts/build.sh
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
#### Manual Build (All Platforms)
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
**Note:** The manual build method requires you to have all dependencies properly configured. For most users, the platform-specific build scripts are recommended.
|
||||
|
||||
**Linux Compatibility:** The project has been tested and successfully builds on Fedora Linux with system packages. It automatically detects the platform and uses appropriate dependency resolution methods (vcpkg for Windows, system packages for Linux/macOS).
|
||||
|
||||
## Usage
|
||||
|
||||
### API Functions
|
||||
### Text Paste with Formatting
|
||||
|
||||
```cpp
|
||||
// Create a new paste
|
||||
int create_paste(const char* server_url, const char* content,
|
||||
const char* password, const char* expiration,
|
||||
const char* format, int burn_after_reading,
|
||||
int open_discussion, char** paste_url,
|
||||
char** delete_token);
|
||||
The library supports multiple text formats as defined in the [PrivateBin API v1.3](https://github.com/PrivateBin/PrivateBin/wiki/API):
|
||||
|
||||
// Retrieve a paste
|
||||
int get_paste(const char* server_url, const char* paste_id,
|
||||
const char* key, char** content);
|
||||
#### Available Formats
|
||||
|
||||
// Delete a paste
|
||||
int delete_paste(const char* server_url, const char* paste_id,
|
||||
const char* delete_token);
|
||||
- **`plaintext`** - Plain text (default)
|
||||
- **`syntaxhighlighting`** - Syntax highlighting for code
|
||||
- **`markdown`** - Markdown formatting
|
||||
|
||||
// Free memory allocated by the API functions
|
||||
void free_string(char* str);
|
||||
```
|
||||
#### Basic Example
|
||||
|
||||
### Example
|
||||
|
||||
```cpp
|
||||
```c
|
||||
#include "privatebinapi.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
int result = create_paste(
|
||||
"https://privatebin.net",
|
||||
"Hello, PrivateBin!",
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"plaintext", // Plain text format
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&paste_url,
|
||||
&delete_token
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "Paste created: " << paste_url << std::endl;
|
||||
std::cout << "Delete token: " << delete_token << std::endl;
|
||||
|
||||
// Free allocated memory
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
} else {
|
||||
std::cout << "Failed to create paste: " << result << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
int result = create_paste(
|
||||
"https://privatebin.net",
|
||||
"Hello, World!",
|
||||
NULL, // no password
|
||||
"1day", // expiration
|
||||
"plaintext", // format
|
||||
0, // don't burn after reading
|
||||
0 // no discussion
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
printf("Paste created: %s\n", paste_url);
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
}
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
#### Syntax Highlighting Example
|
||||
|
||||
- 0: Success
|
||||
- 1: Network error
|
||||
- 2: Encryption/decryption error
|
||||
- 3: Invalid input
|
||||
- 4: Server error
|
||||
- 5: JSON parsing error
|
||||
```c
|
||||
const char* code_content = R"(
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
)";
|
||||
|
||||
int result = create_paste(
|
||||
"https://privatebin.net",
|
||||
code_content,
|
||||
NULL, // no password
|
||||
"1week", // expiration
|
||||
"syntaxhighlighting", // syntax highlighting format
|
||||
0, // don't burn after reading
|
||||
0 // no discussion
|
||||
);
|
||||
```
|
||||
|
||||
#### Markdown Example
|
||||
|
||||
```c
|
||||
const char* markdown_content = R"(
|
||||
# Header
|
||||
|
||||
This is **bold** and *italic* text.
|
||||
|
||||
## Code Block
|
||||
```cpp
|
||||
int x = 42;
|
||||
std::cout << x << std::endl;
|
||||
```
|
||||
|
||||
> This is a blockquote.
|
||||
)";
|
||||
|
||||
int result = create_paste(
|
||||
"https://privatebin.net",
|
||||
markdown_content,
|
||||
NULL, // no password
|
||||
"1month", // expiration
|
||||
"markdown", // markdown format
|
||||
0, // don't burn after reading
|
||||
0 // no discussion
|
||||
);
|
||||
```
|
||||
|
||||
### File Upload
|
||||
|
||||
```c
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
int result = upload_file(
|
||||
"https://privatebin.net",
|
||||
"/path/to/file.txt",
|
||||
"mypassword", // optional password
|
||||
"1week", // expiration
|
||||
0, // don't burn after reading
|
||||
0 // no discussion
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
printf("Successfully uploaded!\n");
|
||||
printf("URL: %s\n", paste_url);
|
||||
printf("Delete Token: %s\n", delete_token);
|
||||
|
||||
// Free memory
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The library contains a comprehensive example program that demonstrates both text paste and file upload functionality:
|
||||
|
||||
### Running the Example
|
||||
|
||||
```bash
|
||||
# Run basic example (tests all text formats)
|
||||
./example
|
||||
|
||||
# Upload a file
|
||||
./example --upload https://privatebin.net test.txt
|
||||
|
||||
# Upload with password and expiration
|
||||
./example --upload https://privatebin.net test.txt mypassword 1week
|
||||
|
||||
# Upload with all options
|
||||
./example --upload https://privatebin.net test.txt mypassword 1day 1 0
|
||||
```
|
||||
|
||||
The basic example now demonstrates:
|
||||
- **Plaintext format** - Simple text pastes
|
||||
- **Syntax highlighting format** - Code with syntax highlighting
|
||||
- **Markdown format** - Rich text formatting
|
||||
- **Interactive format selection** - Shows available options
|
||||
|
||||
### Example Output
|
||||
|
||||
The example program demonstrates:
|
||||
- Creating text pastes
|
||||
- Retrieving paste content
|
||||
- Deleting pastes
|
||||
- File upload with various options
|
||||
- Error handling for all operations
|
||||
|
||||
## API Reference
|
||||
|
||||
### Functions
|
||||
|
||||
- `create_paste()` - Creates a text paste with optional formatting
|
||||
- `upload_file()` - Uploads a file
|
||||
- `get_paste()` - Retrieves a paste
|
||||
- `delete_paste()` - Deletes a paste
|
||||
- `free_string()` - Frees memory
|
||||
|
||||
### Format Support
|
||||
|
||||
The `create_paste()` function supports the following formats as defined in the [PrivateBin API v1.3](https://github.com/PrivateBin/PrivateBin/wiki/API):
|
||||
|
||||
- **`plaintext`** - Default format for simple text
|
||||
- **`syntaxhighlighting`** - Syntax highlighting for code snippets
|
||||
- **`markdown`** - Markdown formatting for rich text
|
||||
|
||||
### Error Codes
|
||||
|
||||
- `0` - Success
|
||||
- `1` - Network error
|
||||
- `2` - Cryptographic error
|
||||
- `3` - Invalid input (e.g., file not found or too large)
|
||||
- `4` - Server error
|
||||
- `5` - JSON parsing error
|
||||
|
||||
## Security
|
||||
|
||||
- **Client-Side Encryption**: All data is encrypted before upload
|
||||
- **AES-256-GCM**: Modern encryption with authentication
|
||||
- **PBKDF2**: Secure key derivation with 100,000 iterations
|
||||
- **Random Keys**: Each paste receives a unique key
|
||||
- **No Server Logs**: Server cannot read encrypted data
|
||||
- **File Compression**: Automatic zlib compression before encryption
|
||||
- **Binary Support**: All file types are treated as encrypted binary data
|
||||
|
||||
## Limitations
|
||||
|
||||
- **File Size**: Maximum file size is 100MB
|
||||
- **File Type**: All file types are treated as binary data
|
||||
- **Server Compatibility**: Works with PrivateBin v1.3+ servers
|
||||
- **Format**: Files are always treated as "plaintext" (even if they are binary data)
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library returns detailed error codes and logs errors to the console. Common errors:
|
||||
|
||||
- **File not found**: Check the file path
|
||||
- **File too large**: Reduce file size or split it up
|
||||
- **Network error**: Check server URL and internet connection
|
||||
- **Server error**: The server might be temporarily unavailable
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
## Troubleshooting
|
||||
## Changelog
|
||||
|
||||
- vcpkg requires a baseline / "this vcpkg instance requires a manifest with a specified baseline"
|
||||
- Run in the repo root to add an initial builtin baseline to `vcpkg.json`:
|
||||
```powershell
|
||||
$env:VCPKG_ROOT = "$env:USERPROFILE\vcpkg"
|
||||
& "$env:VCPKG_ROOT\vcpkg.exe" x-update-baseline --add-initial-baseline
|
||||
```
|
||||
- Then configure/build again.
|
||||
### v0.1.1.6 (2025-08-28)
|
||||
- **NEW**: Full Linux compatibility with system packages
|
||||
- **NEW**: Automatic platform detection (Windows: vcpkg, Linux: system packages)
|
||||
- **IMPROVED**: Enhanced Linux build documentation and instructions
|
||||
- **IMPROVED**: Better dependency management for cross-platform builds
|
||||
- **FIXED**: CMakeLists.txt now works on both Windows and Linux without manual configuration
|
||||
|
||||
- Visual Studio instance not found / "could not find specified instance of Visual Studio"
|
||||
- Ensure VS 2022 Build Tools or Community with C++ tools and Windows 11 SDK are installed.
|
||||
- Use the Developer Command Prompt (VsDevCmd):
|
||||
```powershell
|
||||
cmd /c "call `"C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\Common7\\Tools\\VsDevCmd.bat`" -arch=x64 && build.bat"
|
||||
```
|
||||
- Or delete the build folder to clear stale CMake cache and reconfigure:
|
||||
```powershell
|
||||
Remove-Item -Recurse -Force build
|
||||
```
|
||||
### v0.1.1.5 (2025-08-28)
|
||||
- **NEW**: Enhanced text format support (plaintext, syntax highlighting, markdown)
|
||||
- **NEW**: Comprehensive format testing in examples and integration tests
|
||||
- **IMPROVED**: Better API documentation with format examples
|
||||
- **IMPROVED**: Enhanced test coverage for all supported formats
|
||||
|
||||
- `vswhere.exe` not found
|
||||
- Add the VS Installer directory to PATH for the current session:
|
||||
```powershell
|
||||
$env:PATH = 'C:\\Program Files\\Microsoft Visual Studio\\Installer;' + $env:PATH
|
||||
```
|
||||
- Or install/download `vswhere` from Microsoft and place it under the Installer folder.
|
||||
### v0.1.1.4 (2025-08-28)
|
||||
- **NEW**: Automated release creation script (`scripts/create_release.ps1`)
|
||||
- **NEW**: Build scripts moved to `scripts/` directory for better organization
|
||||
- **IMPROVED**: Enhanced build documentation with platform-specific instructions
|
||||
- **IMPROVED**: Better project structure and organization
|
||||
|
||||
- `cryptoppConfig.cmake` / `cryptopp-config.cmake` not found during CMake configure
|
||||
- Make sure CMake uses vcpkg's toolchain file and that the ports are installed for the active triplet:
|
||||
```powershell
|
||||
$env:VCPKG_ROOT = "$env:USERPROFILE\vcpkg"
|
||||
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
& "$env:VCPKG_ROOT\vcpkg.exe" install cryptopp nlohmann-json --triplet x64-windows
|
||||
```
|
||||
### v0.1.1.1 (2025-08-28)
|
||||
- **NEW**: Combined example program with both text and file upload functionality
|
||||
- **IMPROVED**: Unified command-line interface for examples
|
||||
- **IMPROVED**: Better error handling and user experience
|
||||
|
||||
- PowerShell line continuation issues / parser errors
|
||||
- Prefer single-line commands in PowerShell (avoid backticks if unsure). The README uses single-line examples for reliability.
|
||||
|
||||
## LLVM/clang-cl Coverage (Windows)
|
||||
|
||||
Requirements:
|
||||
- clang/clang-cl toolchain installed
|
||||
- LLVM tools on PATH (`llvm-profdata`, `llvm-cov`)
|
||||
|
||||
Configure with coverage:
|
||||
```powershell
|
||||
cmd /c "call ""C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\VsDevCmd.bat"" -arch=x64 && cmake -S . -B build-llvm -G "Ninja" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang-cl -DENABLE_LLVM_COVERAGE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=""%USERPROFILE%\vcpkg\scripts\buildsystems\vcpkg.cmake"" && cmake --build build-llvm --config Release"
|
||||
```
|
||||
|
||||
Run coverage target (executes tests, merges profiles, generates HTML report):
|
||||
```powershell
|
||||
cmd /c "call ""C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\VsDevCmd.bat"" -arch=x64 && cmake --build build-llvm --target coverage_llvm --config Release && start build-llvm\coverage\html\index.html"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- If Ninja is not available, you can use the VS generator, but Ninja is recommended for clang.
|
||||
- You can override `LLVM_PROFDATA` and `LLVM_COV` cache variables to absolute tool paths if needed.
|
||||
### v0.1.1 (2025-08-28)
|
||||
- **NEW**: File upload functionality added
|
||||
- **NEW**: `upload_file()` function implemented
|
||||
- **NEW**: Comprehensive example program
|
||||
- **NEW**: Extended documentation for file upload
|
||||
- **IMPROVED**: Better error handling
|
||||
- **IMPROVED**: Cross-platform compatibility
|
||||
20
build.bat
20
build.bat
@ -1,20 +0,0 @@
|
||||
@echo off
|
||||
echo Building PrivateBin API C++ DLL...
|
||||
|
||||
REM Create build directory
|
||||
if not exist "build" mkdir build
|
||||
cd build
|
||||
|
||||
REM Generate build files with CMake and vcpkg manifest
|
||||
cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||
|
||||
REM Build the project
|
||||
cmake --build . --config Release
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo Build completed successfully!
|
||||
) else (
|
||||
echo Build failed with error level %ERRORLEVEL%
|
||||
)
|
||||
|
||||
cd ..
|
||||
15
build.sh
15
build.sh
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Building PrivateBin API C++ DLL..."
|
||||
|
||||
# Create build directory
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
# Generate build files with CMake
|
||||
cmake ..
|
||||
|
||||
# Build the project
|
||||
make
|
||||
|
||||
echo "Build completed!"
|
||||
cd ..
|
||||
@ -3,34 +3,15 @@ project(PrivateBinAPIExample)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Use the prebuilt library from the parent build directory
|
||||
set(PRIVATEBINAPI_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../build")
|
||||
set(PRIVATEBINAPI_RELEASE_LIB "${PRIVATEBINAPI_BUILD_DIR}/Release/privatebinapi.lib")
|
||||
|
||||
if(EXISTS "${PRIVATEBINAPI_RELEASE_LIB}")
|
||||
set(PRIVATEBINAPI_LIB "${PRIVATEBINAPI_RELEASE_LIB}")
|
||||
else()
|
||||
# Fallback: try the build root (multi-config generators may place libs differently)
|
||||
find_library(PRIVATEBINAPI_LIB privatebinapi PATHS "${PRIVATEBINAPI_BUILD_DIR}")
|
||||
endif()
|
||||
|
||||
if(NOT PRIVATEBINAPI_LIB)
|
||||
message(FATAL_ERROR "privatebinapi library not found. Please run build.bat in the project root first.")
|
||||
endif()
|
||||
|
||||
# Build example and link against the in-tree target
|
||||
add_executable(example example.cpp)
|
||||
target_link_libraries(example PRIVATE privatebinapi)
|
||||
|
||||
target_link_libraries(example PRIVATE ${PRIVATEBINAPI_LIB} winhttp)
|
||||
|
||||
target_include_directories(example PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
||||
)
|
||||
|
||||
# Install/run helpers for tests: copy DLL next to example on Windows
|
||||
# On Windows, copy the DLL next to the example for easy execution
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET example POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${PRIVATEBINAPI_BUILD_DIR}/Release/privatebinapi.dll
|
||||
$<TARGET_FILE:privatebinapi>
|
||||
$<TARGET_FILE_DIR:example>
|
||||
)
|
||||
endif()
|
||||
@ -1,101 +1,346 @@
|
||||
#include "privatebinapi.h"
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
int main() {
|
||||
// Helper function to wait between API calls to avoid rate limiting
|
||||
void wait_between_calls() {
|
||||
std::cout << "[privatebinapi] Waiting 12 seconds to avoid rate limiting..." << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(12));
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "PrivateBin API C++ DLL Example" << std::endl;
|
||||
std::cout << "===============================" << std::endl;
|
||||
|
||||
// Example of how to use the API
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
std::cout << "Creating paste on https://privatebin.medisoftware.org..." << std::endl;
|
||||
|
||||
// Testing against https://privatebin.medisoftware.org
|
||||
int result = create_paste(
|
||||
"https://privatebin.medisoftware.org", // Server URL
|
||||
"Hello, PrivateBin from C++ Library!", // Content
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"plaintext", // Plain text format
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&paste_url, // Output: paste URL
|
||||
&delete_token // Output: delete token
|
||||
);
|
||||
|
||||
std::cout << "create_paste returned: " << result << std::endl;
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "Paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << paste_url << std::endl;
|
||||
std::cout << "Delete token: " << delete_token << std::endl;
|
||||
|
||||
// Parse paste_id and key from URL (format: "/?{pasteID}#{key}")
|
||||
std::string full_url = paste_url ? paste_url : "";
|
||||
std::string paste_id;
|
||||
std::string key;
|
||||
auto qpos = full_url.find('?');
|
||||
auto hpos = full_url.find('#');
|
||||
if (qpos != std::string::npos) {
|
||||
if (hpos != std::string::npos && hpos > qpos + 1) {
|
||||
paste_id = full_url.substr(qpos + 1, hpos - (qpos + 1));
|
||||
key = full_url.substr(hpos + 1);
|
||||
} else if (qpos + 1 < full_url.size()) {
|
||||
paste_id = full_url.substr(qpos + 1);
|
||||
}
|
||||
// Check if file upload mode is requested
|
||||
if (argc >= 3 && strcmp(argv[1], "--upload") == 0) {
|
||||
// File upload mode
|
||||
if (argc < 4) {
|
||||
std::cout << "Usage: " << argv[0] << " --upload <server_url> <file_path> [password] [expiration] [burn_after_reading] [open_discussion]" << std::endl;
|
||||
std::cout << "Example: " << argv[0] << " --upload https://privatebin.net test.txt mypassword 1day 0 0" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Parameters:" << std::endl;
|
||||
std::cout << " server_url - URL of the PrivateBin server" << std::endl;
|
||||
std::cout << " file_path - Path to the file to upload" << std::endl;
|
||||
std::cout << " password - Optional: Password for the paste (default: none)" << std::endl;
|
||||
std::cout << " expiration - Optional: Expiration time (default: 1day)" << std::endl;
|
||||
std::cout << " burn_after_reading - Optional: Burn after reading (0=no, 1=yes, default: 0)" << std::endl;
|
||||
std::cout << " open_discussion - Optional: Allow discussion (0=no, 1=yes, default: 0)" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Try to fetch paste content back
|
||||
if (!paste_id.empty() && !key.empty()) {
|
||||
std::cout << "Fetching paste..." << std::endl;
|
||||
char* content = nullptr;
|
||||
int gr = get_paste("https://privatebin.medisoftware.org", paste_id.c_str(), key.c_str(), &content);
|
||||
std::cout << "get_paste returned: " << gr << std::endl;
|
||||
if (gr == 0 && content) {
|
||||
std::cout << "Content: " << content << std::endl;
|
||||
free_string(content);
|
||||
}
|
||||
}
|
||||
const char* server_url = argv[2];
|
||||
const char* file_path = argv[3];
|
||||
const char* password = (argc > 4) ? argv[4] : nullptr;
|
||||
const char* expiration = (argc > 5) ? argv[5] : "1day";
|
||||
int burn_after_reading = (argc > 6) ? std::atoi(argv[6]) : 0;
|
||||
int open_discussion = (argc > 7) ? std::atoi(argv[7]) : 0;
|
||||
|
||||
// Try to delete paste
|
||||
if (!paste_id.empty() && delete_token) {
|
||||
std::cout << "Deleting paste..." << std::endl;
|
||||
int dr = delete_paste("https://privatebin.medisoftware.org", paste_id.c_str(), delete_token);
|
||||
std::cout << "delete_paste returned: " << dr << std::endl;
|
||||
std::cout << "PrivateBin File Upload Example" << std::endl;
|
||||
std::cout << "===============================" << std::endl;
|
||||
std::cout << "Server: " << server_url << std::endl;
|
||||
std::cout << "File: " << file_path << std::endl;
|
||||
if (password) {
|
||||
std::cout << "Password: " << password << std::endl;
|
||||
}
|
||||
std::cout << "Expiration: " << expiration << std::endl;
|
||||
std::cout << "Burn after reading: " << (burn_after_reading ? "Yes" : "No") << std::endl;
|
||||
std::cout << "Allow discussion: " << (open_discussion ? "Yes" : "No") << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Clean up allocated memory
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
} else {
|
||||
std::cout << "Failed to create paste. Error code: " << result << std::endl;
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
std::cout << "Uploading file..." << std::endl;
|
||||
|
||||
// Print error codes explanation
|
||||
switch(result) {
|
||||
case 1:
|
||||
std::cout << "Network error occurred." << std::endl;
|
||||
break;
|
||||
case 2:
|
||||
std::cout << "Encryption/decryption error occurred." << std::endl;
|
||||
break;
|
||||
case 3:
|
||||
std::cout << "Invalid input provided." << std::endl;
|
||||
break;
|
||||
case 4:
|
||||
std::cout << "Server error occurred." << std::endl;
|
||||
break;
|
||||
case 5:
|
||||
std::cout << "JSON parsing error occurred." << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Unknown error occurred." << std::endl;
|
||||
break;
|
||||
int result = upload_file(server_url, file_path, password, expiration,
|
||||
burn_after_reading, open_discussion,
|
||||
&paste_url, &delete_token);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "Successfully uploaded!" << std::endl;
|
||||
std::cout << "Paste URL: " << paste_url << std::endl;
|
||||
std::cout << "Delete Token: " << delete_token << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Note: Keep the delete token safe to delete the paste later." << std::endl;
|
||||
|
||||
// Free memory
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
} else {
|
||||
std::cout << "Error uploading file!" << std::endl;
|
||||
std::cout << "Error code: " << result << std::endl;
|
||||
|
||||
switch (result) {
|
||||
case 1:
|
||||
std::cout << "Error: Network problem" << std::endl;
|
||||
break;
|
||||
case 2:
|
||||
std::cout << "Error: Cryptographic error" << std::endl;
|
||||
break;
|
||||
case 3:
|
||||
std::cout << "Error: Invalid input (e.g., file not found or too large)" << std::endl;
|
||||
break;
|
||||
case 4:
|
||||
std::cout << "Error: Server error" << std::endl;
|
||||
break;
|
||||
case 5:
|
||||
std::cout << "Error: JSON parsing error" << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Error: Unknown error" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (result == 0) ? 0 : 1;
|
||||
} else {
|
||||
// Interactive mode - show usage and run basic example
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << " " << argv[0] << " - Run basic example" << std::endl;
|
||||
std::cout << " " << argv[0] << " --upload <args> - Upload a file" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Example of how to use the API with different formats
|
||||
std::cout << "Testing different text formats..." << std::endl;
|
||||
|
||||
// Test 1: Plain text format
|
||||
std::cout << "\n1. Testing plaintext format..." << std::endl;
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
int result = create_paste(
|
||||
"https://privatebin.medisoftware.org", // Server URL
|
||||
"Hello, PrivateBin from C++ Library!\nThis is plain text.", // Content
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"plaintext", // Plain text format
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&paste_url, // Output: paste URL
|
||||
&delete_token // Output: delete token
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "✓ Plaintext paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << paste_url << std::endl;
|
||||
std::cout << "Delete token: " << delete_token << std::endl;
|
||||
|
||||
// Clean up
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
} else {
|
||||
std::cout << "✗ Plaintext paste failed. Error code: " << result << std::endl;
|
||||
}
|
||||
|
||||
// Wait between API calls to avoid rate limiting
|
||||
wait_between_calls();
|
||||
|
||||
// Test 2: Syntax highlighting format
|
||||
std::cout << "\n2. Testing syntax highlighting format..." << std::endl;
|
||||
char* code_paste_url = nullptr;
|
||||
char* code_delete_token = nullptr;
|
||||
|
||||
const char* code_content = R"(
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
)";
|
||||
|
||||
result = create_paste(
|
||||
"https://privatebin.medisoftware.org", // Server URL
|
||||
code_content, // C++ code content
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"syntaxhighlighting", // Syntax highlighting format
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&code_paste_url, // Output: paste URL
|
||||
&code_delete_token // Output: delete token
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "✓ Syntax highlighting paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << code_paste_url << std::endl;
|
||||
std::cout << "Delete token: " << code_delete_token << std::endl;
|
||||
|
||||
// Clean up
|
||||
free_string(code_paste_url);
|
||||
free_string(code_delete_token);
|
||||
} else {
|
||||
std::cout << "✗ Syntax highlighting paste failed. Error code: " << result << std::endl;
|
||||
}
|
||||
|
||||
// Wait between API calls to avoid rate limiting
|
||||
wait_between_calls();
|
||||
|
||||
// Test 3: Markdown format
|
||||
std::cout << "\n3. Testing markdown format..." << std::endl;
|
||||
char* markdown_paste_url = nullptr;
|
||||
char* markdown_delete_token = nullptr;
|
||||
|
||||
const char* markdown_content = R"(
|
||||
# PrivateBin API C++ Library
|
||||
|
||||
## Features
|
||||
- **Text Paste Creation**: Create encrypted text pastes
|
||||
- **File Upload**: Upload files as encrypted pastes
|
||||
- **Multiple Formats**: Support for plaintext, syntax highlighting, and markdown
|
||||
|
||||
## Code Example
|
||||
```cpp
|
||||
int result = create_paste(server_url, content, password, expiration, format,
|
||||
burn_after_reading, open_discussion, &paste_url, &delete_token);
|
||||
```
|
||||
|
||||
> This is a blockquote demonstrating markdown support.
|
||||
)";
|
||||
|
||||
result = create_paste(
|
||||
"https://privatebin.medisoftware.org", // Server URL
|
||||
markdown_content, // Markdown content
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"markdown", // Markdown format
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&markdown_paste_url, // Output: paste URL
|
||||
&markdown_delete_token // Output: delete token
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "✓ Markdown paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << markdown_paste_url << std::endl;
|
||||
std::cout << "Delete token: " << markdown_delete_token << std::endl;
|
||||
|
||||
// Clean up
|
||||
free_string(markdown_paste_url);
|
||||
free_string(markdown_delete_token);
|
||||
} else {
|
||||
std::cout << "✗ Markdown paste failed. Error code: " << result << std::endl;
|
||||
}
|
||||
|
||||
// Wait between API calls to avoid rate limiting
|
||||
wait_between_calls();
|
||||
|
||||
// Test 4: Interactive format selection
|
||||
std::cout << "\n4. Interactive format selection..." << std::endl;
|
||||
char* interactive_paste_url = nullptr;
|
||||
char* interactive_delete_token = nullptr;
|
||||
|
||||
std::cout << "Available formats:" << std::endl;
|
||||
std::cout << " - plaintext (default)" << std::endl;
|
||||
std::cout << " - syntaxhighlighting" << std::endl;
|
||||
std::cout << " - markdown" << std::endl;
|
||||
|
||||
const char* interactive_content = "This paste demonstrates the interactive format selection feature.";
|
||||
|
||||
result = create_paste(
|
||||
"https://privatebin.medisoftware.org", // Server URL
|
||||
interactive_content, // Content
|
||||
nullptr, // No password
|
||||
"1hour", // Expire in 1 hour
|
||||
"plaintext", // Format (can be changed)
|
||||
0, // Don't burn after reading
|
||||
0, // No discussion
|
||||
&interactive_paste_url, // Output: paste URL
|
||||
&interactive_delete_token // Output: delete token
|
||||
);
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "✓ Interactive paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << interactive_paste_url << std::endl;
|
||||
std::cout << "Delete token: " << interactive_delete_token << std::endl;
|
||||
|
||||
// Clean up
|
||||
free_string(interactive_paste_url);
|
||||
free_string(interactive_delete_token);
|
||||
} else {
|
||||
std::cout << "✗ Interactive paste failed. Error code: " << result << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\nAll format tests completed!" << std::endl;
|
||||
return 0;
|
||||
|
||||
std::cout << "create_paste returned: " << result << std::endl;
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << "Paste created successfully!" << std::endl;
|
||||
std::cout << "URL: " << paste_url << std::endl;
|
||||
std::cout << "Delete token: " << delete_token << std::endl;
|
||||
|
||||
// Parse paste_id and key from URL (format: "/?{pasteID}#{key}")
|
||||
std::string full_url = paste_url ? paste_url : "";
|
||||
std::string paste_id;
|
||||
std::string key;
|
||||
auto qpos = full_url.find('?');
|
||||
auto hpos = full_url.find('#');
|
||||
if (qpos != std::string::npos) {
|
||||
if (hpos != std::string::npos && hpos > qpos + 1) {
|
||||
paste_id = full_url.substr(qpos + 1, hpos - (qpos + 1));
|
||||
key = full_url.substr(hpos + 1);
|
||||
} else if (qpos + 1 < full_url.size()) {
|
||||
paste_id = full_url.substr(qpos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to fetch paste content back
|
||||
if (!paste_id.empty() && !key.empty()) {
|
||||
std::cout << "Fetching paste..." << std::endl;
|
||||
char* content = nullptr;
|
||||
int gr = get_paste("https://privatebin.medisoftware.org", paste_id.c_str(), key.c_str(), &content);
|
||||
std::cout << "get_paste returned: " << gr << std::endl;
|
||||
if (gr == 0 && content) {
|
||||
std::cout << "Content: " << content << std::endl;
|
||||
free_string(content);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to delete paste
|
||||
if (!paste_id.empty() && delete_token) {
|
||||
std::cout << "Deleting paste..." << std::endl;
|
||||
int dr = delete_paste("https://privatebin.medisoftware.org", paste_id.c_str(), delete_token);
|
||||
std::cout << "delete_paste returned: " << dr << std::endl;
|
||||
}
|
||||
|
||||
// Clean up allocated memory
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
} else {
|
||||
std::cout << "Failed to create paste. Error code: " << result << std::endl;
|
||||
|
||||
// Print error codes explanation
|
||||
switch(result) {
|
||||
case 1:
|
||||
std::cout << "Network error occurred." << std::endl;
|
||||
break;
|
||||
case 2:
|
||||
std::cout << "Encryption/decryption error occurred." << std::endl;
|
||||
break;
|
||||
case 3:
|
||||
std::cout << "Invalid input provided." << std::endl;
|
||||
break;
|
||||
case 4:
|
||||
std::cout << "Server error occurred." << std::endl;
|
||||
break;
|
||||
case 5:
|
||||
std::cout << "JSON parsing error occurred." << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Unknown error occurred." << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Example completed." << std::endl;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::cout << "Example completed." << std::endl;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -35,6 +35,24 @@ PRIVATEBIN_API int create_paste(const char* server_url, const char* content,
|
||||
int open_discussion, char** paste_url,
|
||||
char** delete_token);
|
||||
|
||||
/**
|
||||
* Uploads a file to a PrivateBin server
|
||||
*
|
||||
* @param server_url The URL of the PrivateBin server
|
||||
* @param file_path The path to the file to upload
|
||||
* @param password Optional password for the paste (can be NULL)
|
||||
* @param expiration Expiration time ("5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never")
|
||||
* @param burn_after_reading Set to 1 to enable burn after reading, 0 to disable
|
||||
* @param open_discussion Set to 1 to enable discussion, 0 to disable
|
||||
* @param paste_url Output parameter for the URL of the created paste
|
||||
* @param delete_token Output parameter for the deletion token
|
||||
* @return 0 on success, error code on failure
|
||||
*/
|
||||
PRIVATEBIN_API int upload_file(const char* server_url, const char* file_path,
|
||||
const char* password, const char* expiration,
|
||||
int burn_after_reading, int open_discussion,
|
||||
char** paste_url, char** delete_token);
|
||||
|
||||
/**
|
||||
* Retrieves a paste from a PrivateBin server
|
||||
*
|
||||
|
||||
22
scripts/build.sh
Normal file
22
scripts/build.sh
Normal file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
echo "Building PrivateBin API C++ (library + example)"
|
||||
|
||||
# Ensure vcpkg exists
|
||||
VCPKG_ROOT="${VCPKG_ROOT:-$HOME/vcpkg}"
|
||||
if [ ! -d "$VCPKG_ROOT" ]; then
|
||||
echo "vcpkg not found under $VCPKG_ROOT, cloning..."
|
||||
git clone https://github.com/microsoft/vcpkg.git "$VCPKG_ROOT"
|
||||
"$VCPKG_ROOT/bootstrap-vcpkg.sh" -disableMetrics
|
||||
fi
|
||||
|
||||
# Clean and configure
|
||||
rm -rf build
|
||||
cmake -S . -B build -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
|
||||
|
||||
# Build all targets
|
||||
cmake --build build --config Release
|
||||
|
||||
echo "Build completed. Artifacts:"
|
||||
echo " - Library: build/libprivatebinapi.so"
|
||||
echo " - Example: build/example/example"
|
||||
62
scripts/build_windows.ps1
Normal file
62
scripts/build_windows.ps1
Normal file
@ -0,0 +1,62 @@
|
||||
# Requires: PowerShell, Visual Studio 2022 Build Tools (C++), vcpkg (will be bootstrapped), Git
|
||||
# Usage:
|
||||
# powershell -NoProfile -ExecutionPolicy Bypass -File .\build_windows.ps1
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Write-Host "Building PrivateBin API C++ (Windows, x64, Release)" -ForegroundColor Cyan
|
||||
|
||||
# Resolve vcpkg root
|
||||
if (-not $env:VCPKG_ROOT) {
|
||||
$env:VCPKG_ROOT = Join-Path $env:USERPROFILE 'vcpkg'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $env:VCPKG_ROOT)) {
|
||||
Write-Host "Cloning vcpkg into $env:VCPKG_ROOT ..."
|
||||
git clone https://github.com/microsoft/vcpkg $env:VCPKG_ROOT
|
||||
& "$env:VCPKG_ROOT\bootstrap-vcpkg.bat"
|
||||
}
|
||||
|
||||
# Choose generator (VS 2022)
|
||||
$generator = 'Visual Studio 17 2022'
|
||||
$arch = 'x64'
|
||||
|
||||
# Clean and configure
|
||||
if (Test-Path build) { Remove-Item -Recurse -Force build }
|
||||
cmake -S . -B build -G "$generator" -A $arch -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake"
|
||||
|
||||
# Build
|
||||
cmake --build build --config Release
|
||||
|
||||
Write-Host "Build completed." -ForegroundColor Green
|
||||
|
||||
# Package artifacts (zip)
|
||||
$dist = Join-Path (Get-Location) 'dist\windows'
|
||||
if (Test-Path $dist) { Remove-Item -Recurse -Force $dist }
|
||||
New-Item -ItemType Directory -Force -Path $dist | Out-Null
|
||||
|
||||
# Collect artifacts
|
||||
$binDir = Join-Path (Get-Location) 'build\Release'
|
||||
$exampleDir = Join-Path (Get-Location) 'build\example\Release'
|
||||
|
||||
$dll = Join-Path $binDir 'privatebinapi.dll'
|
||||
$lib = Join-Path $binDir 'privatebinapi.lib'
|
||||
$pdb = Join-Path $binDir 'privatebinapi.pdb'
|
||||
$exe = Join-Path $exampleDir 'example.exe'
|
||||
|
||||
Copy-Item -Path $dll -Destination $dist -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path $lib -Destination $dist -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path $pdb -Destination $dist -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path $exe -Destination $dist -ErrorAction SilentlyContinue
|
||||
Copy-Item -Path (Join-Path (Get-Location) 'include') -Destination $dist -Recurse
|
||||
Copy-Item -Path (Join-Path (Get-Location) 'README.md') -Destination $dist
|
||||
Copy-Item -Path (Join-Path (Get-Location) 'LICENSE') -Destination $dist -ErrorAction SilentlyContinue
|
||||
|
||||
$zipPath = Join-Path (Get-Location) 'dist\lib-privatebin-v0.1.1.3-windows-x64.zip'
|
||||
if (Test-Path $zipPath) { Remove-Item -Force $zipPath }
|
||||
Compress-Archive -Path (Join-Path $dist '*') -DestinationPath $zipPath
|
||||
|
||||
Write-Host "Windows artifact packaged:" -ForegroundColor Cyan
|
||||
Write-Host " $zipPath"
|
||||
|
||||
34
scripts/collect_binaries.ps1
Normal file
34
scripts/collect_binaries.ps1
Normal file
@ -0,0 +1,34 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$OutDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
|
||||
$items = @(
|
||||
@{ Path = 'build/Release/privatebinapi.dll'; Optional = $false }
|
||||
@{ Path = 'build/Release/privatebinapi.lib'; Optional = $true }
|
||||
@{ Path = 'build/Release/privatebinapi.pdb'; Optional = $true }
|
||||
@{ Path = 'build/example/Release/example.exe'; Optional = $true }
|
||||
)
|
||||
|
||||
foreach ($it in $items) {
|
||||
$p = Resolve-Path -LiteralPath $it.Path -ErrorAction SilentlyContinue
|
||||
if ($null -ne $p) {
|
||||
Copy-Item -LiteralPath $p.Path -Destination $OutDir -Force
|
||||
Write-Host ("Collected: " + [IO.Path]::GetFileName($p.Path))
|
||||
} elseif (-not $it.Optional) {
|
||||
throw "Required artifact not found: $($it.Path)"
|
||||
} else {
|
||||
Write-Host ("Skip missing optional: " + $it.Path)
|
||||
}
|
||||
}
|
||||
|
||||
Get-ChildItem -LiteralPath $OutDir -File | Format-Table Name,Length -AutoSize
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
148
scripts/create_release.ps1
Normal file
148
scripts/create_release.ps1
Normal file
@ -0,0 +1,148 @@
|
||||
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.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_thinkpad.bat") {
|
||||
Write-Host "Verwende scripts\build_thinkpad.bat..." -ForegroundColor Yellow
|
||||
cmd /c scripts\build_thinkpad.bat
|
||||
} else {
|
||||
Write-Host "Verwende scripts\build_windows.ps1..." -ForegroundColor Yellow
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\build_windows.ps1
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Build fehlgeschlagen!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Build erfolgreich abgeschlossen!" -ForegroundColor Green
|
||||
|
||||
# Änderungen committen
|
||||
$status = git status --porcelain
|
||||
if ($status) {
|
||||
git add -A
|
||||
git commit -m "Release $newVersion prepare for release"
|
||||
git push origin HEAD
|
||||
}
|
||||
|
||||
# Tag erstellen und pushen
|
||||
git tag -a $newVersion -m "Release $newVersion"
|
||||
git push origin $newVersion
|
||||
|
||||
# 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_thinkpad.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://gitea.medisoftware.org/api/v1/repos/Markus/lib-privatebin/releases"
|
||||
$release = Invoke-RestMethod -Uri $releaseUri -Method Post -Headers $headers -Body $releaseData
|
||||
|
||||
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://gitea.medisoftware.org/api/v1/repos/Markus/lib-privatebin/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 abgeschlossen!" -ForegroundColor Green
|
||||
|
||||
39
scripts/gen_checksums.ps1
Normal file
39
scripts/gen_checksums.ps1
Normal file
@ -0,0 +1,39 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ZipPath,
|
||||
[Parameter(Mandatory=$true)][string]$BinDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ZipPath)) {
|
||||
throw "ZIP not found: $ZipPath"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $BinDir)) {
|
||||
throw "BinDir not found: $BinDir"
|
||||
}
|
||||
|
||||
function Write-ChecksumFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Path
|
||||
)
|
||||
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash.ToLower()
|
||||
$outfile = "$Path.sha256"
|
||||
$line = "$hash $([System.IO.Path]::GetFileName($Path))"
|
||||
Set-Content -Path $outfile -NoNewline -Encoding ASCII -Value $line
|
||||
Write-Host "Wrote $outfile"
|
||||
}
|
||||
|
||||
# ZIP checksum
|
||||
Write-ChecksumFile -Path $ZipPath
|
||||
|
||||
# Binaries checksums
|
||||
Get-ChildItem -LiteralPath $BinDir -File | ForEach-Object {
|
||||
Write-ChecksumFile -Path $_.FullName
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
47
scripts/gitea_upload.ps1
Normal file
47
scripts/gitea_upload.ps1
Normal file
@ -0,0 +1,47 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Server,
|
||||
[Parameter(Mandatory=$true)][string]$Owner,
|
||||
[Parameter(Mandatory=$true)][string]$Repo,
|
||||
[Parameter(Mandatory=$true)][string]$Token,
|
||||
[Parameter(Mandatory=$true)][string]$ZipPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ZipPath)) {
|
||||
throw "ZIP not found: $ZipPath"
|
||||
}
|
||||
|
||||
$headers = @{ Authorization = "token $Token" }
|
||||
|
||||
$latestUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/latest"
|
||||
$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri $latestUrl
|
||||
if (-not $latest -or -not $latest.id) {
|
||||
throw "Failed to get latest release from $latestUrl"
|
||||
}
|
||||
|
||||
$rid = [string]$latest.id
|
||||
$name = [System.IO.Path]::GetFileName($ZipPath)
|
||||
$escapedName = [System.Uri]::EscapeDataString($name)
|
||||
$uploadUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/$rid/assets?name=$escapedName"
|
||||
|
||||
Write-Host "Latest release id: $rid"
|
||||
Write-Host "Uploading $name to $uploadUrl"
|
||||
|
||||
# Use Invoke-WebRequest multipart form upload
|
||||
$form = @{ attachment = Get-Item -LiteralPath $ZipPath }
|
||||
$resp = Invoke-WebRequest -Headers $headers -Method Post -Uri $uploadUrl -Form $form
|
||||
|
||||
if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) {
|
||||
Write-Host "Upload successful (HTTP $($resp.StatusCode))"
|
||||
} else {
|
||||
Write-Host "Upload failed (HTTP $($resp.StatusCode))" -ForegroundColor Red
|
||||
if ($resp.Content) { Write-Host $resp.Content }
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
49
scripts/gitea_upload_all.ps1
Normal file
49
scripts/gitea_upload_all.ps1
Normal file
@ -0,0 +1,49 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Server,
|
||||
[Parameter(Mandatory=$true)][string]$Owner,
|
||||
[Parameter(Mandatory=$true)][string]$Repo,
|
||||
[Parameter(Mandatory=$true)][string]$Token,
|
||||
[Parameter(Mandatory=$true)][string]$Directory
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Directory)) {
|
||||
throw "Directory not found: $Directory"
|
||||
}
|
||||
|
||||
$headers = @{ Authorization = "token $Token" }
|
||||
|
||||
$latestUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/latest"
|
||||
$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri $latestUrl
|
||||
if (-not $latest -or -not $latest.id) {
|
||||
throw "Failed to get latest release from $latestUrl"
|
||||
}
|
||||
$rid = [string]$latest.id
|
||||
Write-Host "Latest release id: $rid"
|
||||
|
||||
$files = Get-ChildItem -LiteralPath $Directory -Recurse -File
|
||||
if (-not $files) {
|
||||
Write-Host "No files to upload in $Directory"
|
||||
exit 0
|
||||
}
|
||||
|
||||
foreach ($f in $files) {
|
||||
$name = $f.Name
|
||||
$escapedName = [System.Uri]::EscapeDataString($name)
|
||||
$uploadUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/$rid/assets?name=$escapedName"
|
||||
Write-Host "Uploading $name ..."
|
||||
try {
|
||||
$resp = Invoke-WebRequest -Headers $headers -Method Post -Uri $uploadUrl -Form @{ attachment = $f }
|
||||
Write-Host " OK ($($resp.StatusCode))"
|
||||
}
|
||||
catch {
|
||||
Write-Host " FAIL: $name -> $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
89
scripts/manage_release.ps1
Normal file
89
scripts/manage_release.ps1
Normal file
@ -0,0 +1,89 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Server,
|
||||
[Parameter(Mandatory=$true)][string]$Owner,
|
||||
[Parameter(Mandatory=$true)][string]$Repo,
|
||||
[Parameter(Mandatory=$true)][string]$Token
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$headers = @{ Authorization = "token $Token" }
|
||||
|
||||
# Get latest release
|
||||
$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/latest"
|
||||
if (-not $latest -or -not $latest.id) {
|
||||
throw "Failed to get latest release"
|
||||
}
|
||||
$rid = [string]$latest.id
|
||||
Write-Host "Operating on release id: $rid"
|
||||
|
||||
# Load full release including attachments/assets list
|
||||
$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid"
|
||||
$assets = $release.assets
|
||||
if (-not $assets) { $assets = $release.attachments }
|
||||
|
||||
if ($assets) {
|
||||
$groups = $assets | Group-Object -Property name
|
||||
foreach ($g in $groups) {
|
||||
$sorted = $g.Group | Sort-Object -Property id -Descending
|
||||
if ($sorted.Count -gt 1) {
|
||||
$keep = $sorted[0]
|
||||
foreach ($extra in $sorted[1..($sorted.Count-1)]) {
|
||||
if ($null -ne $extra -and $extra.id -ne $keep.id) {
|
||||
$delUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/assets/$($extra.id)"
|
||||
try {
|
||||
Invoke-RestMethod -Headers $headers -Method DELETE -Uri $delUrl | Out-Null
|
||||
Write-Host ("Deleted duplicate asset: " + $extra.name + " (id=" + $extra.id + ")")
|
||||
} catch {
|
||||
Write-Host ("Failed to delete asset id=" + $extra.id + ": " + $_.Exception.Message) -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host 'No assets found on release.'
|
||||
}
|
||||
|
||||
# Compose release notes with known checksums if they exist locally
|
||||
$zipShaPath = (Resolve-Path 'dist\lib-privatebin-v0.1.1.3-windows-x64.zip.sha256' -ErrorAction SilentlyContinue)
|
||||
$dllShaPath = (Resolve-Path 'dist\windows_bin\privatebinapi.dll.sha256' -ErrorAction SilentlyContinue)
|
||||
$libShaPath = (Resolve-Path 'dist\windows_bin\privatebinapi.lib.sha256' -ErrorAction SilentlyContinue)
|
||||
$exeShaPath = (Resolve-Path 'dist\windows_bin\example.exe.sha256' -ErrorAction SilentlyContinue)
|
||||
|
||||
$lines = @('# Downloadable artifacts', '')
|
||||
$lines += ("- [lib-privatebin-v0.1.1.3-windows-x64.zip]($Server/$Owner/$Repo/releases/download/$tag/lib-privatebin-v0.1.1.3-windows-x64.zip)")
|
||||
$lines += ("- [privatebinapi.dll]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.dll)")
|
||||
$lines += ("- [privatebinapi.lib]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.lib)")
|
||||
$lines += ("- [example.exe]($Server/$Owner/$Repo/releases/download/$tag/example.exe)")
|
||||
$lines += ''
|
||||
$lines += '# SHA256 checksums'
|
||||
if ($zipShaPath) { $lines += ("- [lib-privatebin-v0.1.1.3-windows-x64.zip.sha256]($Server/$Owner/$Repo/releases/download/$tag/lib-privatebin-v0.1.1.3-windows-x64.zip.sha256)") }
|
||||
if ($dllShaPath) { $lines += ("- [privatebinapi.dll.sha256]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.dll.sha256)") }
|
||||
if ($libShaPath) { $lines += ("- [privatebinapi.lib.sha256]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.lib.sha256)") }
|
||||
if ($exeShaPath) { $lines += ("- [example.exe.sha256]($Server/$Owner/$Repo/releases/download/$tag/example.exe.sha256)") }
|
||||
|
||||
$lines += ''
|
||||
$lines += '# Changes'
|
||||
try {
|
||||
$changelog = Get-Content -Raw -LiteralPath 'CHANGELOG.md'
|
||||
$sections = $changelog -split "\r?\n## "
|
||||
$section = $sections | Where-Object { $_ -like 'v0.1.1.3*' } | Select-Object -First 1
|
||||
if ($section) {
|
||||
$secLines = $section -split "\r?\n"
|
||||
if ($secLines[0] -like 'v0.1.1.3*') { $secLines[0] = 'v0.1.1.3' }
|
||||
$lines += $secLines
|
||||
} else {
|
||||
$lines += 'See CHANGELOG.md'
|
||||
}
|
||||
} catch {
|
||||
$lines += 'See CHANGELOG.md'
|
||||
}
|
||||
|
||||
$bodyText = ($lines -join "`n")
|
||||
$payload = @{ body = $bodyText } | ConvertTo-Json -Depth 3
|
||||
|
||||
Invoke-RestMethod -Headers (@{ Authorization = ("token " + $Token); 'Content-Type' = 'application/json' }) -Method PATCH -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" -Body $payload | Out-Null
|
||||
Write-Host 'Release notes updated.'
|
||||
|
||||
|
||||
80
scripts/prune_release_assets.ps1
Normal file
80
scripts/prune_release_assets.ps1
Normal file
@ -0,0 +1,80 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Server,
|
||||
[Parameter(Mandatory=$true)][string]$Owner,
|
||||
[Parameter(Mandatory=$true)][string]$Repo,
|
||||
[Parameter(Mandatory=$true)][string]$Token
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$headers = @{ Authorization = "token $Token" }
|
||||
|
||||
$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/latest"
|
||||
if (-not $latest -or -not $latest.id) { throw 'Failed to get latest release' }
|
||||
$rid = [string]$latest.id
|
||||
$tag = $latest.tag_name
|
||||
Write-Host "Pruning assets on release id=$rid (tag=$tag)"
|
||||
|
||||
$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid"
|
||||
$assets = $release.assets
|
||||
if (-not $assets) { $assets = $release.attachments }
|
||||
|
||||
# Allowed asset names
|
||||
$allowed = @(
|
||||
'lib-privatebin-v0.1.1.3-windows-x64.zip',
|
||||
'lib-privatebin-v0.1.1.3-windows-x64.zip.sha256',
|
||||
'privatebinapi.dll','privatebinapi.dll.sha256',
|
||||
'privatebinapi.lib','privatebinapi.lib.sha256',
|
||||
'example.exe','example.exe.sha256'
|
||||
)
|
||||
|
||||
if ($assets) {
|
||||
foreach ($a in $assets) {
|
||||
if ($allowed -notcontains $a.name) {
|
||||
$delUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/assets/$($a.id)"
|
||||
try {
|
||||
Invoke-RestMethod -Headers $headers -Method DELETE -Uri $delUrl | Out-Null
|
||||
Write-Host ("Deleted: " + $a.name)
|
||||
} catch {
|
||||
Write-Host ("Skip delete (" + $a.name + "): " + $_.Exception.Message) -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host 'No assets found.'
|
||||
}
|
||||
|
||||
# Re-fetch assets after prune
|
||||
$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid"
|
||||
$assets = $release.assets; if (-not $assets) { $assets = $release.attachments }
|
||||
|
||||
# Build markdown with links
|
||||
$lines = @('# Downloadable artifacts', '')
|
||||
foreach ($name in $allowed) {
|
||||
$a = $assets | Where-Object { $_.name -eq $name } | Select-Object -First 1
|
||||
if ($a) {
|
||||
# Prefer deterministic download URL pattern
|
||||
$url = "$Server/$Owner/$Repo/releases/download/$tag/$name"
|
||||
$lines += ("- [$name]($url)")
|
||||
}
|
||||
}
|
||||
|
||||
$lines += ''
|
||||
$lines += '# SHA256 checksums'
|
||||
foreach ($name in $allowed | Where-Object { $_.EndsWith('.sha256') }) {
|
||||
$a = $assets | Where-Object { $_.name -eq $name } | Select-Object -First 1
|
||||
if ($a) {
|
||||
$url = "$Server/$Owner/$Repo/releases/download/$tag/$name"
|
||||
$lines += ("- [$name]($url)")
|
||||
}
|
||||
}
|
||||
|
||||
$bodyText = ($lines -join "`n")
|
||||
$payload = @{ body = $bodyText } | ConvertTo-Json -Depth 3
|
||||
Invoke-RestMethod -Headers (@{ Authorization = ("token " + $Token); 'Content-Type' = 'application/json' }) -Method PATCH -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" -Body $payload | Out-Null
|
||||
Write-Host 'Release notes updated with links.'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -16,18 +16,19 @@ std::string Base58::encode(const std::vector<unsigned char>& data) {
|
||||
}
|
||||
|
||||
// Convert to base58
|
||||
std::vector<unsigned char> digits((data.size() - leading_zeros) * 138 / 100 + 1);
|
||||
size_t digits_size = (data.size() - leading_zeros) * 138 / 100 + 1;
|
||||
std::vector<unsigned char> digits(digits_size);
|
||||
size_t digitslen = 1;
|
||||
|
||||
for (size_t i = leading_zeros; i < data.size(); i++) {
|
||||
unsigned int carry = data[i];
|
||||
size_t carry = static_cast<size_t>(data[i]);
|
||||
for (size_t j = 0; j < digitslen; j++) {
|
||||
carry += (unsigned int)(digits[j]) << 8;
|
||||
digits[j] = carry % 58;
|
||||
carry += static_cast<size_t>(digits[j]) << 8;
|
||||
digits[j] = static_cast<unsigned char>(carry % 58);
|
||||
carry /= 58;
|
||||
}
|
||||
while (carry > 0) {
|
||||
digits[digitslen++] = carry % 58;
|
||||
digits[digitslen++] = static_cast<unsigned char>(carry % 58);
|
||||
carry /= 58;
|
||||
}
|
||||
}
|
||||
@ -56,22 +57,23 @@ std::vector<unsigned char> Base58::decode(const std::string& encoded) {
|
||||
}
|
||||
|
||||
// Convert from base58
|
||||
std::vector<unsigned char> bytes((encoded.length() - leading_ones) * 733 / 1000 + 1);
|
||||
size_t bytes_size = (encoded.length() - leading_ones) * 733 / 1000 + 1;
|
||||
std::vector<unsigned char> bytes(bytes_size);
|
||||
size_t byteslen = 1;
|
||||
|
||||
for (size_t i = leading_ones; i < encoded.length(); i++) {
|
||||
unsigned int carry = ALPHABET.find(encoded[i]);
|
||||
size_t carry = static_cast<size_t>(ALPHABET.find(encoded[i]));
|
||||
if (carry == std::string::npos) {
|
||||
throw std::invalid_argument("Invalid character in Base58 string");
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < byteslen; j++) {
|
||||
carry += (unsigned int)(bytes[j]) * 58;
|
||||
bytes[j] = carry & 0xff;
|
||||
carry += static_cast<size_t>(bytes[j]) * 58;
|
||||
bytes[j] = static_cast<unsigned char>(carry & 0xff);
|
||||
carry >>= 8;
|
||||
}
|
||||
while (carry > 0) {
|
||||
bytes[byteslen++] = carry & 0xff;
|
||||
bytes[byteslen++] = static_cast<unsigned char>(carry & 0xff);
|
||||
carry >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,13 +45,13 @@ std::vector<unsigned char> Crypto::encrypt(const std::vector<unsigned char>& pla
|
||||
encryption.EncryptAndAuthenticate(
|
||||
ciphertext.data(),
|
||||
auth_tag.data(),
|
||||
auth_tag.size(),
|
||||
static_cast<int>(auth_tag.size()),
|
||||
iv.data(),
|
||||
iv.size(),
|
||||
static_cast<int>(iv.size()),
|
||||
nullptr,
|
||||
0, // Additional authenticated data
|
||||
plaintext.data(),
|
||||
plaintext.size()
|
||||
static_cast<int>(plaintext.size())
|
||||
);
|
||||
|
||||
return ciphertext;
|
||||
@ -79,13 +79,13 @@ std::vector<unsigned char> Crypto::decrypt(const std::vector<unsigned char>& cip
|
||||
bool valid = decryption.DecryptAndVerify(
|
||||
plaintext.data(),
|
||||
auth_tag.data(),
|
||||
auth_tag.size(),
|
||||
static_cast<int>(auth_tag.size()),
|
||||
iv.data(),
|
||||
iv.size(),
|
||||
static_cast<int>(iv.size()),
|
||||
nullptr,
|
||||
0, // Additional authenticated data
|
||||
ciphertext.data(),
|
||||
ciphertext.size()
|
||||
static_cast<int>(ciphertext.size())
|
||||
);
|
||||
|
||||
if(!valid) {
|
||||
|
||||
@ -585,7 +585,7 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
// PrivateBin API erwartet POST für delete
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#define PRIVATEBIN_API_VERSION "1.3"
|
||||
|
||||
@ -24,14 +26,14 @@ static void copy_string_to_output(const std::string& source, char** destination)
|
||||
if (destination) {
|
||||
*destination = static_cast<char*>(malloc(source.length() + 1));
|
||||
if (*destination) {
|
||||
std::strcpy(*destination, source.c_str());
|
||||
strcpy_s(*destination, source.length() + 1, source.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int create_paste(const char* server_url, const char* content,
|
||||
PRIVATEBIN_API int create_paste(const char* server_url, const char* content,
|
||||
const char* password, const char* expiration,
|
||||
const char* format, int burn_after_reading,
|
||||
int open_discussion, char** paste_url,
|
||||
@ -126,7 +128,127 @@ int create_paste(const char* server_url, const char* content,
|
||||
}
|
||||
}
|
||||
|
||||
int get_paste(const char* server_url, const char* paste_id,
|
||||
PRIVATEBIN_API int upload_file(const char* server_url, const char* file_path,
|
||||
const char* password, const char* expiration,
|
||||
int burn_after_reading, int open_discussion,
|
||||
char** paste_url, char** delete_token) {
|
||||
|
||||
if (!server_url || !file_path) {
|
||||
return ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read the file content
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "[privatebinapi] Failed to open file: " << file_path << std::endl;
|
||||
return ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
file.seekg(0, std::ios::end);
|
||||
std::streamsize file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Check if file is too large (PrivateBin typically has limits)
|
||||
if (file_size > 100 * 1024 * 1024) { // 100MB limit
|
||||
std::cerr << "[privatebinapi] File too large: " << file_size << " bytes" << std::endl;
|
||||
return ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Read file content into vector
|
||||
std::vector<unsigned char> file_content(file_size);
|
||||
if (!file.read(reinterpret_cast<char*>(file_content.data()), file_size)) {
|
||||
std::cerr << "[privatebinapi] Failed to read file content" << std::endl;
|
||||
return ERROR_INVALID_INPUT;
|
||||
}
|
||||
file.close();
|
||||
|
||||
// Generate a random 32-byte paste key
|
||||
std::vector<unsigned char> paste_key = Crypto::generate_key(32);
|
||||
|
||||
// If password provided, append it to the paste key
|
||||
std::string paste_passphrase(reinterpret_cast<char*>(paste_key.data()), paste_key.size());
|
||||
if (password) {
|
||||
paste_passphrase += password;
|
||||
}
|
||||
|
||||
// Compress the file content
|
||||
std::cerr << "[privatebinapi] compress file..." << std::endl;
|
||||
std::vector<unsigned char> compressed_data = Crypto::compress(file_content);
|
||||
|
||||
// Generate random salt and IV
|
||||
std::vector<unsigned char> salt = Crypto::generate_key(8);
|
||||
std::vector<unsigned char> iv = Crypto::generate_key(16);
|
||||
|
||||
// Derive key using PBKDF2
|
||||
std::cerr << "[privatebinapi] pbkdf2..." << std::endl;
|
||||
std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256(
|
||||
paste_passphrase, salt, 100000, 32);
|
||||
|
||||
// Encrypt the data
|
||||
std::cerr << "[privatebinapi] encrypt file..." << std::endl;
|
||||
std::vector<unsigned char> auth_tag;
|
||||
std::vector<unsigned char> cipher_text = Crypto::encrypt(
|
||||
compressed_data, derived_key, iv, auth_tag);
|
||||
|
||||
// Create the JSON structure for file upload
|
||||
json paste_json = JsonParser::create_paste_json(
|
||||
cipher_text, auth_tag, iv, salt,
|
||||
expiration ? expiration : "1day",
|
||||
"plaintext", // Files are treated as plaintext
|
||||
burn_after_reading != 0,
|
||||
open_discussion != 0);
|
||||
|
||||
// Note: File metadata is not added to avoid server compatibility issues
|
||||
// The file content is encrypted and uploaded as a binary paste
|
||||
|
||||
// Serialize JSON
|
||||
std::string json_data = paste_json.dump();
|
||||
|
||||
// Send POST request
|
||||
HttpClient client;
|
||||
std::string response;
|
||||
if (!client.post(server_url, json_data, response)) {
|
||||
return ERROR_NETWORK;
|
||||
}
|
||||
|
||||
// Parse response
|
||||
int status;
|
||||
std::string message, paste_id, url, del_token;
|
||||
if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) {
|
||||
std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl;
|
||||
return ERROR_JSON_PARSE;
|
||||
}
|
||||
if (status != 0) {
|
||||
std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl;
|
||||
}
|
||||
|
||||
if (status != 0) {
|
||||
return ERROR_SERVER;
|
||||
}
|
||||
|
||||
// Encode the paste key with Base58
|
||||
std::string encoded_key = Base58::encode(paste_key);
|
||||
|
||||
// Construct the full URL
|
||||
std::string full_url = url + "#" + encoded_key;
|
||||
|
||||
// Copy results to output parameters
|
||||
copy_string_to_output(full_url, paste_url);
|
||||
copy_string_to_output(del_token, delete_token);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[privatebinapi] file upload error: " << e.what() << std::endl;
|
||||
return ERROR_CRYPTO;
|
||||
} catch (...) {
|
||||
std::cerr << "[privatebinapi] unknown file upload error" << std::endl;
|
||||
return ERROR_CRYPTO;
|
||||
}
|
||||
}
|
||||
|
||||
PRIVATEBIN_API int get_paste(const char* server_url, const char* paste_id,
|
||||
const char* key, char** content) {
|
||||
|
||||
if (!server_url || !paste_id || !key || !content) {
|
||||
@ -183,7 +305,7 @@ int get_paste(const char* server_url, const char* paste_id,
|
||||
}
|
||||
}
|
||||
|
||||
int delete_paste(const char* server_url, const char* paste_id,
|
||||
PRIVATEBIN_API int delete_paste(const char* server_url, const char* paste_id,
|
||||
const char* delete_token) {
|
||||
|
||||
if (!server_url || !paste_id || !delete_token) {
|
||||
@ -225,7 +347,7 @@ int delete_paste(const char* server_url, const char* paste_id,
|
||||
}
|
||||
}
|
||||
|
||||
void free_string(char* str) {
|
||||
PRIVATEBIN_API void free_string(char* str) {
|
||||
if (str) {
|
||||
free(str);
|
||||
}
|
||||
|
||||
12
test_file.txt
Normal file
12
test_file.txt
Normal file
@ -0,0 +1,12 @@
|
||||
This is a test file for the PrivateBin API File Upload functionality.
|
||||
|
||||
The file contains:
|
||||
- German umlauts: äöüß
|
||||
- Special characters: !@#$%^&*()_+-=[]{}|;':",./<>?
|
||||
- Numbers: 0123456789
|
||||
- Multi-line text
|
||||
|
||||
This file is used to test whether the upload_file function works correctly.
|
||||
|
||||
PrivateBin API v1.3
|
||||
File Upload Feature successfully implemented!
|
||||
@ -21,27 +21,35 @@ static bool extract_paste_id_and_key(const std::string& full_url, std::string& p
|
||||
}
|
||||
|
||||
int main() {
|
||||
const char* it = std::getenv("PRIVATEBIN_IT");
|
||||
char* it = nullptr;
|
||||
size_t len = 0;
|
||||
_dupenv_s(&it, &len, "PRIVATEBIN_IT");
|
||||
if (!it || std::string(it) == "0") {
|
||||
std::cout << "[test] PRIVATEBIN_IT not set; skipping integration test." << std::endl;
|
||||
free(it);
|
||||
return 0; // treat as success when integration testing is disabled
|
||||
}
|
||||
|
||||
const char* server = "https://privatebin.medisoftware.org/";
|
||||
const char* content = "Integration test from lib-privatebin";
|
||||
const char* password = nullptr; // no password
|
||||
const char* expiration = "5min"; // short-lived
|
||||
const char* format = "plaintext";
|
||||
const int burn_after_reading = 0;
|
||||
const int open_discussion = 0;
|
||||
|
||||
std::cout << "[test] Testing different text formats..." << std::endl;
|
||||
|
||||
// Test 1: Plaintext format
|
||||
std::cout << "[test] 1. Testing plaintext format..." << std::endl;
|
||||
const char* plaintext_content = "Integration test from lib-privatebin - plaintext";
|
||||
const char* plaintext_format = "plaintext";
|
||||
|
||||
char* paste_url = nullptr;
|
||||
char* delete_token = nullptr;
|
||||
|
||||
std::cout << "[test] create_paste..." << std::endl;
|
||||
int rc = create_paste(server, content, password, expiration, format,
|
||||
int rc = create_paste(server, plaintext_content, password, expiration, plaintext_format,
|
||||
burn_after_reading, open_discussion, &paste_url, &delete_token);
|
||||
if (rc != 0 || paste_url == nullptr || delete_token == nullptr) {
|
||||
std::cerr << "[test][ERROR] create_paste failed, rc=" << rc << std::endl;
|
||||
std::cerr << "[test][ERROR] plaintext create_paste failed, rc=" << rc << std::endl;
|
||||
if (paste_url) free_string(paste_url);
|
||||
if (delete_token) free_string(delete_token);
|
||||
return 1;
|
||||
@ -68,7 +76,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
std::string fetched_str = fetched;
|
||||
assert(fetched_str == content && "fetched content mismatch");
|
||||
assert(fetched_str == plaintext_content && "fetched plaintext content mismatch");
|
||||
|
||||
std::cout << "[test] delete_paste..." << std::endl;
|
||||
rc = delete_paste(server, paste_id.c_str(), delete_token);
|
||||
@ -80,11 +88,142 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
// cleanup plaintext test
|
||||
free_string(paste_url);
|
||||
free_string(delete_token);
|
||||
free_string(fetched);
|
||||
|
||||
// Test 2: Syntax highlighting format
|
||||
std::cout << "[test] 2. Testing syntax highlighting format..." << std::endl;
|
||||
const char* code_content = R"(
|
||||
#include <iostream>
|
||||
|
||||
std::cout << "[test] all API functions passed." << std::endl;
|
||||
int main() {
|
||||
std::cout << "Hello, World!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
)";
|
||||
const char* code_format = "syntaxhighlighting";
|
||||
|
||||
char* code_paste_url = nullptr;
|
||||
char* code_delete_token = nullptr;
|
||||
|
||||
rc = create_paste(server, code_content, password, expiration, code_format,
|
||||
burn_after_reading, open_discussion, &code_paste_url, &code_delete_token);
|
||||
if (rc != 0 || code_paste_url == nullptr || code_delete_token == nullptr) {
|
||||
std::cerr << "[test][ERROR] syntax highlighting create_paste failed, rc=" << rc << std::endl;
|
||||
if (code_paste_url) free_string(code_paste_url);
|
||||
if (code_delete_token) free_string(code_delete_token);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse code paste URL
|
||||
std::string code_full_url = code_paste_url;
|
||||
std::string code_paste_id;
|
||||
std::string code_key;
|
||||
const bool code_ok_parse = extract_paste_id_and_key(code_full_url, code_paste_id, code_key);
|
||||
if (!code_ok_parse) {
|
||||
std::cerr << "[test][ERROR] failed to parse code paste id/key from URL: " << code_full_url << std::endl;
|
||||
free_string(code_paste_url);
|
||||
free_string(code_delete_token);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch and verify code paste
|
||||
char* code_fetched = nullptr;
|
||||
rc = get_paste(server, code_paste_id.c_str(), code_key.c_str(), &code_fetched);
|
||||
if (rc != 0 || code_fetched == nullptr) {
|
||||
std::cerr << "[test][ERROR] code get_paste failed, rc=" << rc << std::endl;
|
||||
free_string(code_paste_url);
|
||||
free_string(code_delete_token);
|
||||
return 1;
|
||||
}
|
||||
std::string code_fetched_str = code_fetched;
|
||||
assert(code_fetched_str == code_content && "fetched code content mismatch");
|
||||
|
||||
// Delete code paste
|
||||
rc = delete_paste(server, code_paste_id.c_str(), code_delete_token);
|
||||
if (rc != 0) {
|
||||
std::cerr << "[test][ERROR] code delete_paste failed, rc=" << rc << std::endl;
|
||||
free_string(code_paste_url);
|
||||
free_string(code_delete_token);
|
||||
free_string(code_fetched);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cleanup code test
|
||||
free_string(code_paste_url);
|
||||
free_string(code_delete_token);
|
||||
free_string(code_fetched);
|
||||
|
||||
// Test 3: Markdown format
|
||||
std::cout << "[test] 3. Testing markdown format..." << std::endl;
|
||||
const char* markdown_content = R"(
|
||||
# Test Header
|
||||
|
||||
This is a **bold** and *italic* text.
|
||||
|
||||
## Code Block
|
||||
```cpp
|
||||
int x = 42;
|
||||
std::cout << x << std::endl;
|
||||
```
|
||||
|
||||
> This is a blockquote.
|
||||
)";
|
||||
const char* markdown_format = "markdown";
|
||||
|
||||
char* markdown_paste_url = nullptr;
|
||||
char* markdown_delete_token = nullptr;
|
||||
|
||||
rc = create_paste(server, markdown_content, password, expiration, markdown_format,
|
||||
burn_after_reading, open_discussion, &markdown_paste_url, &markdown_delete_token);
|
||||
if (rc != 0 || markdown_paste_url == nullptr || markdown_delete_token == nullptr) {
|
||||
std::cerr << "[test][ERROR] markdown create_paste failed, rc=" << rc << std::endl;
|
||||
if (markdown_paste_url) free_string(markdown_paste_url);
|
||||
if (markdown_delete_token) free_string(markdown_delete_token);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse markdown paste URL
|
||||
std::string markdown_full_url = markdown_paste_url;
|
||||
std::string markdown_paste_id;
|
||||
std::string markdown_key;
|
||||
const bool markdown_ok_parse = extract_paste_id_and_key(markdown_full_url, markdown_paste_id, markdown_key);
|
||||
if (!markdown_ok_parse) {
|
||||
std::cerr << "[test][ERROR] failed to parse markdown paste id/key from URL: " << markdown_full_url << std::endl;
|
||||
free_string(markdown_paste_url);
|
||||
free_string(markdown_delete_token);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch and verify markdown paste
|
||||
char* markdown_fetched = nullptr;
|
||||
rc = get_paste(server, markdown_paste_id.c_str(), markdown_key.c_str(), &markdown_fetched);
|
||||
if (rc != 0 || markdown_fetched == nullptr) {
|
||||
std::cerr << "[test][ERROR] markdown get_paste failed, rc=" << rc << std::endl;
|
||||
free_string(markdown_paste_url);
|
||||
free_string(markdown_delete_token);
|
||||
return 1;
|
||||
}
|
||||
std::string markdown_fetched_str = markdown_fetched;
|
||||
assert(markdown_fetched_str == markdown_content && "fetched markdown content mismatch");
|
||||
|
||||
// Delete markdown paste
|
||||
rc = delete_paste(server, markdown_paste_id.c_str(), markdown_delete_token);
|
||||
if (rc != 0) {
|
||||
std::cerr << "[test][ERROR] markdown delete_paste failed, rc=" << rc << std::endl;
|
||||
free_string(markdown_paste_url);
|
||||
free_string(markdown_delete_token);
|
||||
free_string(markdown_fetched);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cleanup markdown test
|
||||
free_string(markdown_paste_url);
|
||||
free_string(markdown_delete_token);
|
||||
free_string(markdown_fetched);
|
||||
|
||||
std::cout << "[test] All format tests passed successfully!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user