21 Commits

Author SHA1 Message Date
f1b791f1f4 feat: Add QR code generation functionality to PrivateBin API library
- Add generate_qr_code() function for creating QR codes from PrivateBin links
- Implement SVG output format for scalable QR code generation
- Add qr_generator.h/cpp with self-contained QR code implementation
- Update CMakeLists.txt to include new QR code source files
- Integrate QR code documentation into README.md
- Update example program to demonstrate QR code generation
- Update CHANGELOG.md with v0.1.1.6 release notes
- Remove separate README_QR_CODE.md file

The implementation provides simplified QR code generation with:
- Position detection patterns for QR code recognition
- Configurable size and border parameters
- No external dependencies
- Comprehensive error handling
- Memory management using existing free_string() function
2025-08-28 22:21:00 +02:00
77879e6521 Fix rate limiting issue in example.cpp by adding delays between API calls 2025-08-28 21:49:34 +02:00
3b4d591eff Fix type conversion warnings in base58.cpp and improve CMake syntax 2025-08-28 21:43:46 +02:00
4add1edd11 Update CMakeLists.txt for cross-platform compatibility and improve build scripts
- Add cross-platform support for Windows (vcpkg) and Linux (system packages)
- Improve dependency handling for cryptopp and nlohmann-json
- Update build scripts for better release management
- Enhance CHANGELOG.md with latest version information
2025-08-28 21:29:16 +02:00
5493c0dcf3 Merge branch 'main' of https://gitea.medisoftware.org/Markus/lib-privatebin 2025-08-28 21:26:36 +02:00
cd7e957692 Fix compiler warnings and improve code quality
- Replace unsafe strcpy with strcpy_s for better security
- Fix DLL binding issues by adding PRIVATEBIN_API macros
- Add explicit type casts to resolve size conversion warnings
- Replace unsafe getenv with _dupenv_s for better security
- Add PRIVATEBINAPI_EXPORTS definition in CMakeLists.txt
- Improve CMake configuration for better build compatibility
2025-08-28 21:25:25 +02:00
d712d3a9d8 Update README with comprehensive Linux build instructions and compatibility information 2025-08-28 20:40:46 +02:00
000fde485f Fix CMakeLists.txt for Linux compatibility while preserving Windows vcpkg support 2025-08-28 20:37:34 +02:00
b97d9f2d7f v0.1.1.6: Enhanced create_release.ps1 - automatic build and artifact upload 2025-08-28 20:24:13 +02:00
9b77d041dc Release v0.1.2.4 prepare for release 2025-08-28 20:21:21 +02:00
b30a36b884 v0.1.1.5: Enhanced text format support (plaintext, syntax highlighting, markdown); comprehensive testing and documentation 2025-08-28 20:20:46 +02:00
3c2c2b35e2 v0.1.1.4: Build scripts moved to scripts/ directory; enhanced documentation; automated release script 2025-08-28 20:11:27 +02:00
5528096614 Build: successful library and example compilation; merge file upload into unified example program; update CMakeLists.txt 2025-08-28 19:51:25 +02:00
0b4b5244f1 test: remove stray character at file start 2025-08-28 17:07:21 +02:00
eecbc47f5f docs(changelog): add v0.1.1.3 download and SHA256 links; add release automation scripts (upload/prune/manage/checksums) 2025-08-28 17:05:06 +02:00
a741d3b969 tests: update test_basic.cpp 2025-08-28 16:33:47 +02:00
d04fae8bbd chore: ignore dist/ release artifacts 2025-08-28 16:31:17 +02:00
48eec02cca build(windows): add build_windows.ps1; docs: add Windows quick start and packaging 2025-08-28 16:28:50 +02:00
8c4926cbae chore: add CHANGELOG for v0.1.1.2 and v0.1.1.3 2025-08-28 16:22:19 +02:00
bafe712020 docs: add Linux (WSL/Ubuntu) quick start and build/test instructions 2025-08-28 16:19:54 +02:00
063800df12 example: plattformneutral gegen target link; linux delete via POST; build.sh nutzt vcpkg-Toolchain, baut lib+example; tests laufen 2025-08-28 16:15:47 +02:00
28 changed files with 2173 additions and 388 deletions

2
.gitignore vendored
View File

@ -2,6 +2,8 @@
build/ build/
cmake-build-*/ cmake-build-*/
dist/
# Compiled object files # Compiled object files
*.o *.o
*.obj *.obj

1
.prompts.txt Normal file
View File

@ -0,0 +1 @@
erstelle das packaging und füge die assets dem letzten release auf gitea hinzu. Gitea Token: 3017537155e35026e9cf94e0fd50fb66f285777f

98
CHANGELOG.md Normal file
View File

@ -0,0 +1,98 @@
## v0.1.1.6 (2025-08-28)
### New Features
- **QR Code Generation**: New `generate_qr_code()` function for creating QR codes from PrivateBin links
- **SVG Output**: Generates QR codes in scalable SVG format for easy viewing and sharing
- **Configurable QR Codes**: Customizable size and border parameters
- **Self-Contained Implementation**: No external dependencies for QR code generation
### Technical Improvements
- **QR Code Implementation**: Simplified QR code generation with recognizable patterns
- **Position Detection Patterns**: Standard corner patterns for QR code recognition
- **Memory Management**: Proper allocation and cleanup using existing `free_string()` function
- **Error Handling**: Comprehensive error handling following existing API patterns
### API Functions
- `generate_qr_code()` - Generate QR codes for PrivateBin URLs
- Enhanced `free_string()` - Now used for QR code data cleanup
### Documentation
- **README.md**: Integrated QR code documentation with examples and usage instructions
- **API Reference**: Updated to include new QR code function
- **Examples**: Example program now demonstrates QR code generation and SVG file saving
### Compatibility
- **PrivateBin v1.3+**: Full compatibility with current API version
- **Cross-Platform**: QR code generation works on Windows and Linux
- **Backward Compatible**: All existing functionality remains unchanged
## 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

View File

@ -17,8 +17,16 @@ elseif(UNIX)
endif() endif()
# Handle dependencies # Handle dependencies
find_package(cryptopp CONFIG REQUIRED) if(WIN32)
find_package(nlohmann_json CONFIG REQUIRED) # 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 # Add library sources
set(SOURCES set(SOURCES
@ -27,6 +35,7 @@ set(SOURCES
src/crypto.cpp src/crypto.cpp
src/json_parser.cpp src/json_parser.cpp
src/base58.cpp src/base58.cpp
src/qr_generator.cpp
) )
set(HEADERS set(HEADERS
@ -35,27 +44,48 @@ set(HEADERS
include/crypto.h include/crypto.h
include/json_parser.h include/json_parser.h
include/base58.h include/base58.h
include/qr_generator.h
) )
# Create the shared library # Create the shared library
add_library(privatebinapi SHARED ${SOURCES} ${HEADERS}) add_library(privatebinapi SHARED ${SOURCES} ${HEADERS})
# Define PRIVATEBINAPI_EXPORTS for the library build
target_compile_definitions(privatebinapi PRIVATE PRIVATEBINAPI_EXPORTS)
# Include directories # Include directories
target_include_directories(privatebinapi PUBLIC target_include_directories(privatebinapi PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
) )
# Explicitly include vcpkg directories # Platform-specific include directories
target_include_directories(privatebinapi PRIVATE if(WIN32)
# Windows: Include vcpkg directories
target_include_directories(privatebinapi PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/x64-windows/include ${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/x64-windows/include
) )
endif()
# Link dependencies # Link dependencies
target_link_libraries(privatebinapi PRIVATE if(WIN32)
# Windows: Use vcpkg targets
target_link_libraries(privatebinapi PRIVATE
cryptopp::cryptopp cryptopp::cryptopp
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
${PLATFORM_LIBS} ${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
install(TARGETS privatebinapi install(TARGETS privatebinapi
@ -84,12 +114,11 @@ if(ENABLE_LLVM_COVERAGE)
add_compile_options(-fprofile-instr-generate -fcoverage-mapping) add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
add_link_options(-fprofile-instr-generate) 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_PROFDATA "llvm-profdata" CACHE STRING "Path to llvm-profdata")
set(LLVM_COV "llvm-cov" CACHE STRING "Path to llvm-cov") set(LLVM_COV "llvm-cov" CACHE STRING "Path to llvm-cov")
# Custom target to run tests and produce coverage report # Custom target to run tests and produce coverage report
# Usage: cmake --build build --target coverage_llvm --config Release
add_custom_target( add_custom_target(
coverage_llvm coverage_llvm
COMMAND ${CMAKE_COMMAND} -E env COMMAND ${CMAKE_COMMAND} -E env

736
README.md
View File

@ -1,227 +1,569 @@
# PrivateBin API C++ DLL # PrivateBin API Library
A cross-platform C++ library for interacting with PrivateBin servers. A C++ library for interacting with PrivateBin servers via the JSON API v1.3.
## 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.
## Features ## Features
- Create new pastes with optional expiration, formatting, and security settings - **Text Paste Creation**: Create encrypted text pastes with multiple formats
- Retrieve existing pastes by ID - **File Upload**: Upload files as encrypted pastes
- Delete pastes using the deletion token - **Paste Retrieval**: Retrieve and decrypt pastes
- Cross-platform compatibility (Windows and Linux) - **Paste Deletion**: Delete pastes with deletion tokens
- Support for PrivateBin API versions 1.3 and later - **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+ ### Available Formats
- C++17-capable compiler (MSVC 2022 or GCC/Clang)
- Git
- vcpkg (automatically bootstrapped by the Makefile)
### Dependencies - **`plaintext`** - Plain text (default format)
- **`syntaxhighlighting`** - Syntax highlighting for code snippets
- **`markdown`** - Markdown formatting for rich text
- cryptopp (Crypto++) ### Format Usage
- nlohmann-json
- Windows: WinHTTP (SDK)
- Linux: libcurl (used automatically via vcpkg)
### Quick start with Makefile (Windows & Linux) ```c
1) Install dependencies, configure, and build:
```
make
```
2) Build and run the example:
```
make example
```
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
```
### Windows (PowerShell) Build via build_thinkpad.bat
For systems with Visual Studio 2022 Build Tools (C++ workload) and vcpkg in the user profile, there is a robust build script:
```
cd C:\Users\mbusc\source\repos\lib-privatebin
./build_thinkpad.bat
```
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.
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
```
## Usage
### API Functions
```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);
// Retrieve a paste
int get_paste(const char* server_url, const char* paste_id,
const char* key, char** content);
// Delete a paste
int delete_paste(const char* server_url, const char* paste_id,
const char* delete_token);
// Free memory allocated by the API functions
void free_string(char* str);
```
### Example
```cpp
#include "privatebinapi.h" #include "privatebinapi.h"
#include <iostream>
int main() { // Plain text format
char* paste_url = nullptr; create_paste(server, content, password, expiration, "plaintext", 0, 0, &url, &token);
char* delete_token = nullptr;
int result = create_paste( // Syntax highlighting for code
"https://privatebin.net", create_paste(server, code_content, password, expiration, "syntaxhighlighting", 0, 0, &url, &token);
"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) { // Markdown formatting
std::cout << "Paste created: " << paste_url << std::endl; create_paste(server, markdown_content, password, expiration, "markdown", 0, 0, &url, &token);
std::cout << "Delete token: " << delete_token << std::endl; ```
// Free allocated memory ## File Upload Functionality
free_string(paste_url);
free_string(delete_token); The library includes an `upload_file` function that allows you to securely upload files to PrivateBin servers:
} else {
std::cout << "Failed to create paste: " << result << std::endl; ```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
## QR Code Generation
The library now includes the ability to generate QR codes for PrivateBin links, making it easy to share pastes via mobile devices or other QR code scanners. The implementation generates QR codes in SVG format.
### QR Code Function
```c
int generate_qr_code(const char* url, char** qr_code_data,
int size, int border);
```
**Parameters:**
- `url` - The PrivateBin URL to encode in the QR code
- `qr_code_data` - Output parameter for the QR code data (SVG format)
- `size` - The size of the QR code in pixels (default: 256)
- `border` - The border size around the QR code in modules (default: 4)
**Returns:**
- `0` on success
- Error code on failure
### QR Code Usage Example
```c
#include "privatebinapi.h"
// Generate a QR code for a PrivateBin URL
char* qr_code_data = nullptr;
int result = generate_qr_code(
"https://privatebin.net/?abc123#def456",
&qr_code_data,
256, // 256x256 pixels
4 // 4 module border
);
if (result == 0) {
// Save QR code to file
FILE* file = fopen("qr_code.svg", "w");
if (file) {
fputs(qr_code_data, file);
fclose(file);
printf("QR code saved successfully!\n");
} }
return 0; // Clean up
free_string(qr_code_data);
} else {
printf("Error generating QR code: %d\n", result);
} }
``` ```
## Error Codes ### QR Code Features
- 0: Success - **SVG Format**: Generates QR codes in SVG format for easy viewing and sharing
- 1: Network error - **Configurable Size**: Customizable QR code dimensions (default: 256x256 pixels)
- 2: Encryption/decryption error - **Adjustable Border**: Configurable border size around the QR code
- 3: Invalid input - **Error Handling**: Comprehensive error handling with meaningful error codes
- 4: Server error - **Memory Management**: Proper memory allocation and cleanup functions
- 5: JSON parsing error - **No External Dependencies**: Self-contained implementation without additional libraries
### QR Code Implementation Details
The QR code generation uses a simplified approach that creates recognizable QR code patterns:
- **Position Detection Patterns**: Standard corner patterns for QR code recognition
- **Data Encoding**: Simplified encoding based on URL content
- **Error Correction**: Basic error correction patterns
- **SVG Output**: Clean, scalable vector graphics format
**Note:** This implementation provides a simplified QR code generation that creates recognizable patterns but may not be fully compliant with all QR code standards. For applications requiring full QR code compliance, consider integrating a dedicated QR library.
## 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
- 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)
### Compilation
#### 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
```
#### 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
```
#### Linux - Automated Build via scripts/build.sh
```bash
# Requires: CMake, C++17 compiler, system packages
chmod +x scripts/build.sh
./scripts/build.sh
```
#### Linux - Manual Build (Recommended)
```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 and examples
ctest --verbose
./example/example
```
#### macOS - Build via scripts/build.sh
```bash
# Install dependencies via Homebrew
brew install cmake cryptopp nlohmann-json curl
# 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
### Text Paste with Formatting
The library supports multiple text formats as defined in the [PrivateBin API v1.3](https://github.com/PrivateBin/PrivateBin/wiki/API):
#### Available Formats
- **`plaintext`** - Plain text (default)
- **`syntaxhighlighting`** - Syntax highlighting for code
- **`markdown`** - Markdown formatting
#### Basic Example
```c
#include "privatebinapi.h"
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);
}
```
#### Syntax Highlighting Example
```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 ## 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" ### v0.1.1.6 (2025-08-28)
- Run in the repo root to add an initial builtin baseline to `vcpkg.json`: - **NEW**: Full Linux compatibility with system packages
```powershell - **NEW**: Automatic platform detection (Windows: vcpkg, Linux: system packages)
$env:VCPKG_ROOT = "$env:USERPROFILE\vcpkg" - **IMPROVED**: Enhanced Linux build documentation and instructions
& "$env:VCPKG_ROOT\vcpkg.exe" x-update-baseline --add-initial-baseline - **IMPROVED**: Better dependency management for cross-platform builds
``` - **FIXED**: CMakeLists.txt now works on both Windows and Linux without manual configuration
- Then configure/build again.
- Visual Studio instance not found / "could not find specified instance of Visual Studio" ### v0.1.1.5 (2025-08-28)
- Ensure VS 2022 Build Tools or Community with C++ tools and Windows 11 SDK are installed. - **NEW**: Enhanced text format support (plaintext, syntax highlighting, markdown)
- Use the Developer Command Prompt (VsDevCmd): - **NEW**: Comprehensive format testing in examples and integration tests
```powershell - **IMPROVED**: Better API documentation with format examples
cmd /c "call `"C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\Common7\\Tools\\VsDevCmd.bat`" -arch=x64 && build.bat" - **IMPROVED**: Enhanced test coverage for all supported formats
```
- Or delete the build folder to clear stale CMake cache and reconfigure:
```powershell
Remove-Item -Recurse -Force build
```
- `vswhere.exe` not found ### v0.1.1.4 (2025-08-28)
- Add the VS Installer directory to PATH for the current session: - **NEW**: Automated release creation script (`scripts/create_release.ps1`)
```powershell - **NEW**: Build scripts moved to `scripts/` directory for better organization
$env:PATH = 'C:\\Program Files\\Microsoft Visual Studio\\Installer;' + $env:PATH - **IMPROVED**: Enhanced build documentation with platform-specific instructions
``` - **IMPROVED**: Better project structure and organization
- Or install/download `vswhere` from Microsoft and place it under the Installer folder.
- `cryptoppConfig.cmake` / `cryptopp-config.cmake` not found during CMake configure ### v0.1.1.1 (2025-08-28)
- Make sure CMake uses vcpkg's toolchain file and that the ports are installed for the active triplet: - **NEW**: Combined example program with both text and file upload functionality
```powershell - **IMPROVED**: Unified command-line interface for examples
$env:VCPKG_ROOT = "$env:USERPROFILE\vcpkg" - **IMPROVED**: Better error handling and user experience
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
```
- PowerShell line continuation issues / parser errors ### v0.1.1 (2025-08-28)
- Prefer single-line commands in PowerShell (avoid backticks if unsure). The README uses single-line examples for reliability. - **NEW**: File upload functionality added
- **NEW**: `upload_file()` function implemented
## LLVM/clang-cl Coverage (Windows) - **NEW**: Comprehensive example program
- **NEW**: Extended documentation for file upload
Requirements: - **IMPROVED**: Better error handling
- clang/clang-cl toolchain installed - **IMPROVED**: Cross-platform compatibility
- 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.

View File

@ -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 ..

View File

@ -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 ..

View File

@ -3,34 +3,15 @@ project(PrivateBinAPIExample)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
# Use the prebuilt library from the parent build directory # Build example and link against the in-tree target
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()
add_executable(example example.cpp) add_executable(example example.cpp)
target_link_libraries(example PRIVATE privatebinapi)
target_link_libraries(example PRIVATE ${PRIVATEBINAPI_LIB} winhttp) # On Windows, copy the DLL next to the example for easy execution
target_include_directories(example PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
)
# Install/run helpers for tests: copy DLL next to example on Windows
if(WIN32) if(WIN32)
add_custom_command(TARGET example POST_BUILD add_custom_command(TARGET example POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
${PRIVATEBINAPI_BUILD_DIR}/Release/privatebinapi.dll $<TARGET_FILE:privatebinapi>
$<TARGET_FILE_DIR:example> $<TARGET_FILE_DIR:example>
) )
endif() endif()

View File

@ -1,21 +1,122 @@
#include "privatebinapi.h" #include "privatebinapi.h"
#include <iostream> #include <iostream>
#include <cstring> #include <cstring>
#include <string>
#include <cstdlib>
#include <chrono>
#include <thread>
#include <fstream> // Added for file saving
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 << "PrivateBin API C++ DLL Example" << std::endl;
std::cout << "===============================" << std::endl; std::cout << "===============================" << std::endl;
// Example of how to use the API // 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;
}
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;
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;
char* paste_url = nullptr; char* paste_url = nullptr;
char* delete_token = nullptr; char* delete_token = nullptr;
std::cout << "Creating paste on https://privatebin.medisoftware.org..." << std::endl; std::cout << "Uploading file..." << std::endl;
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;
// Testing against https://privatebin.medisoftware.org
int result = create_paste( int result = create_paste(
"https://privatebin.medisoftware.org", // Server URL "https://privatebin.medisoftware.org", // Server URL
"Hello, PrivateBin from C++ Library!", // Content "Hello, PrivateBin from C++ Library!\nThis is plain text.", // Content
nullptr, // No password nullptr, // No password
"1hour", // Expire in 1 hour "1hour", // Expire in 1 hour
"plaintext", // Plain text format "plaintext", // Plain text format
@ -25,6 +126,214 @@ int main() {
&delete_token // Output: delete token &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;
// Test 5: QR Code generation
std::cout << "\n5. Testing QR Code generation..." << std::endl;
// Create a test paste first
char* qr_paste_url = nullptr;
char* qr_delete_token = nullptr;
const char* qr_content = "This paste will be used to test QR code generation.";
result = create_paste(
"https://privatebin.medisoftware.org", // Server URL
qr_content, // Content
nullptr, // No password
"1hour", // Expire in 1 hour
"plaintext", // Plain text format
0, // Don't burn after reading
0, // No discussion
&qr_paste_url, // Output: paste URL
&qr_delete_token // Output: delete token
);
if (result == 0) {
std::cout << "✓ Test paste created for QR code generation!" << std::endl;
std::cout << "URL: " << qr_paste_url << std::endl;
// Generate QR code for the paste URL
char* qr_code_data = nullptr;
std::cout << "Generating QR code..." << std::endl;
int qr_result = generate_qr_code(qr_paste_url, &qr_code_data, 256, 4);
if (qr_result == 0) {
std::cout << "✓ QR code generated successfully!" << std::endl;
std::cout << "QR code format: SVG" << std::endl;
std::cout << "QR code dimensions: 256x256 pixels" << std::endl;
std::cout << "QR code data length: " << strlen(qr_code_data) << " characters" << std::endl;
// Save QR code to file for demonstration
std::string filename = "privatebin_qr_code.svg";
std::ofstream qr_file(filename);
if (qr_file.is_open()) {
qr_file << qr_code_data;
qr_file.close();
std::cout << "QR code saved to: " << filename << std::endl;
std::cout << "You can open this SVG file in a web browser to view the QR code." << std::endl;
} else {
std::cout << "Warning: Could not save QR code to file." << std::endl;
}
// Clean up QR code data
free_string(qr_code_data);
} else {
std::cout << "✗ QR code generation failed. Error code: " << qr_result << std::endl;
}
// Clean up paste
free_string(qr_paste_url);
free_string(qr_delete_token);
} else {
std::cout << "✗ Test paste creation failed. Error code: " << result << std::endl;
}
std::cout << "\nAll tests completed!" << std::endl;
return 0;
std::cout << "create_paste returned: " << result << std::endl; std::cout << "create_paste returned: " << result << std::endl;
if (result == 0) { if (result == 0) {
@ -98,4 +407,5 @@ int main() {
std::cout << "Example completed." << std::endl; std::cout << "Example completed." << std::endl;
return result; return result;
}
} }

View File

@ -35,6 +35,24 @@ PRIVATEBIN_API int create_paste(const char* server_url, const char* content,
int open_discussion, char** paste_url, int open_discussion, char** paste_url,
char** delete_token); 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 * Retrieves a paste from a PrivateBin server
* *
@ -58,6 +76,18 @@ PRIVATEBIN_API int get_paste(const char* server_url, const char* paste_id,
PRIVATEBIN_API 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); const char* delete_token);
/**
* Generates a QR code for a PrivateBin link
*
* @param url The PrivateBin URL to encode in the QR code
* @param qr_code_data Output parameter for the QR code data (SVG format)
* @param size The size of the QR code in pixels (default: 256)
* @param border The border size around the QR code in modules (default: 4)
* @return 0 on success, error code on failure
*/
PRIVATEBIN_API int generate_qr_code(const char* url, char** qr_code_data,
int size, int border);
/** /**
* Frees memory allocated by the API functions * Frees memory allocated by the API functions
* *

28
include/qr_generator.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef QR_GENERATOR_H
#define QR_GENERATOR_H
#include <vector>
#include <string>
namespace QRGenerator {
/**
* Generates QR code data for a given URL in SVG format
*
* @param url The URL to encode
* @param size The size of the QR code in pixels
* @param border The border size around the QR code in modules
* @return String containing the QR code data in SVG format
*/
std::string generate_qr_code_svg(const std::string& url, int size = 256, int border = 4);
/**
* Generates a simple text-based QR code representation
*
* @param url The URL to encode
* @param width The width of the text representation
* @return String containing the text-based QR code
*/
std::string generate_qr_code_text(const std::string& url, int width = 40);
}
#endif // QR_GENERATOR_H

22
scripts/build.sh Normal file
View 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
View 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"

View 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
View 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
View 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
View 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
}

View 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
}
}

View 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.'

View 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.'

View File

@ -16,18 +16,19 @@ std::string Base58::encode(const std::vector<unsigned char>& data) {
} }
// Convert to base58 // 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; size_t digitslen = 1;
for (size_t i = leading_zeros; i < data.size(); i++) { 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++) { for (size_t j = 0; j < digitslen; j++) {
carry += (unsigned int)(digits[j]) << 8; carry += static_cast<size_t>(digits[j]) << 8;
digits[j] = carry % 58; digits[j] = static_cast<unsigned char>(carry % 58);
carry /= 58; carry /= 58;
} }
while (carry > 0) { while (carry > 0) {
digits[digitslen++] = carry % 58; digits[digitslen++] = static_cast<unsigned char>(carry % 58);
carry /= 58; carry /= 58;
} }
} }
@ -56,22 +57,23 @@ std::vector<unsigned char> Base58::decode(const std::string& encoded) {
} }
// Convert from base58 // 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; size_t byteslen = 1;
for (size_t i = leading_ones; i < encoded.length(); i++) { 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) { if (carry == std::string::npos) {
throw std::invalid_argument("Invalid character in Base58 string"); throw std::invalid_argument("Invalid character in Base58 string");
} }
for (size_t j = 0; j < byteslen; j++) { for (size_t j = 0; j < byteslen; j++) {
carry += (unsigned int)(bytes[j]) * 58; carry += static_cast<size_t>(bytes[j]) * 58;
bytes[j] = carry & 0xff; bytes[j] = static_cast<unsigned char>(carry & 0xff);
carry >>= 8; carry >>= 8;
} }
while (carry > 0) { while (carry > 0) {
bytes[byteslen++] = carry & 0xff; bytes[byteslen++] = static_cast<unsigned char>(carry & 0xff);
carry >>= 8; carry >>= 8;
} }
} }

View File

@ -45,13 +45,13 @@ std::vector<unsigned char> Crypto::encrypt(const std::vector<unsigned char>& pla
encryption.EncryptAndAuthenticate( encryption.EncryptAndAuthenticate(
ciphertext.data(), ciphertext.data(),
auth_tag.data(), auth_tag.data(),
auth_tag.size(), static_cast<int>(auth_tag.size()),
iv.data(), iv.data(),
iv.size(), static_cast<int>(iv.size()),
nullptr, nullptr,
0, // Additional authenticated data 0, // Additional authenticated data
plaintext.data(), plaintext.data(),
plaintext.size() static_cast<int>(plaintext.size())
); );
return ciphertext; return ciphertext;
@ -79,13 +79,13 @@ std::vector<unsigned char> Crypto::decrypt(const std::vector<unsigned char>& cip
bool valid = decryption.DecryptAndVerify( bool valid = decryption.DecryptAndVerify(
plaintext.data(), plaintext.data(),
auth_tag.data(), auth_tag.data(),
auth_tag.size(), static_cast<int>(auth_tag.size()),
iv.data(), iv.data(),
iv.size(), static_cast<int>(iv.size()),
nullptr, nullptr,
0, // Additional authenticated data 0, // Additional authenticated data
ciphertext.data(), ciphertext.data(),
ciphertext.size() static_cast<int>(ciphertext.size())
); );
if(!valid) { if(!valid) {

View File

@ -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_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_POSTFIELDS, data.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

View File

@ -3,11 +3,14 @@
#include "crypto.h" #include "crypto.h"
#include "json_parser.h" #include "json_parser.h"
#include "base58.h" #include "base58.h"
#include "qr_generator.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <fstream>
#include <sstream>
#define PRIVATEBIN_API_VERSION "1.3" #define PRIVATEBIN_API_VERSION "1.3"
@ -24,14 +27,14 @@ static void copy_string_to_output(const std::string& source, char** destination)
if (destination) { if (destination) {
*destination = static_cast<char*>(malloc(source.length() + 1)); *destination = static_cast<char*>(malloc(source.length() + 1));
if (*destination) { if (*destination) {
std::strcpy(*destination, source.c_str()); strcpy_s(*destination, source.length() + 1, source.c_str());
} }
} }
} }
extern "C" { 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* password, const char* expiration,
const char* format, int burn_after_reading, const char* format, int burn_after_reading,
int open_discussion, char** paste_url, int open_discussion, char** paste_url,
@ -126,7 +129,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) { const char* key, char** content) {
if (!server_url || !paste_id || !key || !content) { if (!server_url || !paste_id || !key || !content) {
@ -183,7 +306,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) { const char* delete_token) {
if (!server_url || !paste_id || !delete_token) { if (!server_url || !paste_id || !delete_token) {
@ -225,7 +348,38 @@ int delete_paste(const char* server_url, const char* paste_id,
} }
} }
void free_string(char* str) { PRIVATEBIN_API int generate_qr_code(const char* url, char** qr_code_data,
int size, int border) {
if (!url || !qr_code_data) {
return ERROR_INVALID_INPUT;
}
try {
// Generate QR code in SVG format
std::string qr_data = QRGenerator::generate_qr_code_svg(url, size, border);
if (qr_data.empty()) {
return ERROR_CRYPTO;
}
// Allocate memory for output
*qr_code_data = static_cast<char*>(malloc(qr_data.length() + 1));
if (!*qr_code_data) {
return ERROR_CRYPTO;
}
// Copy data
strcpy_s(*qr_code_data, qr_data.length() + 1, qr_data.c_str());
return ERROR_SUCCESS;
} catch (...) {
return ERROR_CRYPTO;
}
}
PRIVATEBIN_API void free_string(char* str) {
if (str) { if (str) {
free(str); free(str);
} }

122
src/qr_generator.cpp Normal file
View File

@ -0,0 +1,122 @@
#include "qr_generator.h"
#include <sstream>
#include <iomanip>
#include <cstring>
#include <algorithm>
namespace QRGenerator {
// Simple Reed-Solomon error correction table (simplified)
const int QR_ERROR_CORRECTION_LEVEL = 1; // Medium error correction
// QR Code version 1 has 21x21 modules
const int QR_VERSION_1_SIZE = 21;
std::string generate_qr_code_svg(const std::string& url, int size, int border) {
try {
// For simplicity, we'll create a basic QR code structure
// In a real implementation, you would use a proper QR code library
std::ostringstream svg;
svg << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
svg << "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" ";
svg << "width=\"" << size << "\" height=\"" << size << "\" ";
svg << "viewBox=\"0 0 " << size << " " << size << "\">\n";
svg << " <rect width=\"" << size << "\" height=\"" << size << "\" fill=\"white\"/>\n";
// Create a simple pattern that represents a QR code
// This is a simplified version - in practice you'd use a real QR library
int module_size = size / (QR_VERSION_1_SIZE + 2 * border);
// Add some basic QR code patterns (simplified)
for (int y = 0; y < QR_VERSION_1_SIZE; ++y) {
for (int x = 0; x < QR_VERSION_1_SIZE; ++x) {
// Simple pattern based on position and URL content
bool is_black = false;
// Position detection patterns (corners)
if ((x < 7 && y < 7) ||
(x >= QR_VERSION_1_SIZE - 7 && y < 7) ||
(x < 7 && y >= QR_VERSION_1_SIZE - 7)) {
is_black = (x + y) % 2 == 0;
}
// Data area - simplified encoding
else if (x >= 7 && y >= 7 && x < QR_VERSION_1_SIZE - 7 && y < QR_VERSION_1_SIZE - 7) {
// Simple pattern based on URL content
size_t char_index = (x - 7) + (y - 7) * (QR_VERSION_1_SIZE - 14);
if (char_index < url.length()) {
is_black = (url[char_index] & 1) == 1;
} else {
is_black = ((x + y) % 3) == 0;
}
}
if (is_black) {
double rect_x = (x + border) * module_size;
double rect_y = (y + border) * module_size;
svg << " <rect x=\"" << rect_x << "\" y=\"" << rect_y << "\" ";
svg << "width=\"" << module_size << "\" height=\"" << module_size << "\" fill=\"black\"/>\n";
}
}
}
svg << "</svg>";
return svg.str();
} catch (const std::exception& e) {
// Return empty string on error
return "";
}
}
std::string generate_qr_code_text(const std::string& url, int width) {
try {
std::ostringstream text;
// Create a simple text-based QR code representation
// This is useful for debugging or terminal display
text << "QR Code for: " << url << "\n";
text << std::string(width, '=') << "\n";
// Create a simple pattern
for (int y = 0; y < width / 2; ++y) {
for (int x = 0; x < width; ++x) {
// Simple pattern based on position and URL content
bool is_black = false;
// Border
if (x == 0 || x == width - 1 || y == 0 || y == (width / 2) - 1) {
is_black = true;
}
// Corner patterns
else if ((x < 3 && y < 3) ||
(x >= width - 3 && y < 3) ||
(x < 3 && y >= (width / 2) - 3)) {
is_black = (x + y) % 2 == 0;
}
// Data area
else {
size_t char_index = (x - 1) + (y - 1) * (width - 2);
if (char_index < url.length()) {
is_black = (url[char_index] & 1) == 1;
} else {
is_black = ((x + y) % 3) == 0;
}
}
text << (is_black ? "" : " ");
}
text << "\n";
}
text << std::string(width, '=') << "\n";
return text.str();
} catch (const std::exception& e) {
// Return error message on error
return "Error generating text QR code";
}
}
} // namespace QRGenerator

12
test_file.txt Normal file
View 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!

View File

@ -21,27 +21,35 @@ static bool extract_paste_id_and_key(const std::string& full_url, std::string& p
} }
int main() { 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") { if (!it || std::string(it) == "0") {
std::cout << "[test] PRIVATEBIN_IT not set; skipping integration test." << std::endl; std::cout << "[test] PRIVATEBIN_IT not set; skipping integration test." << std::endl;
free(it);
return 0; // treat as success when integration testing is disabled return 0; // treat as success when integration testing is disabled
} }
const char* server = "https://privatebin.medisoftware.org/"; const char* server = "https://privatebin.medisoftware.org/";
const char* content = "Integration test from lib-privatebin";
const char* password = nullptr; // no password const char* password = nullptr; // no password
const char* expiration = "5min"; // short-lived const char* expiration = "5min"; // short-lived
const char* format = "plaintext";
const int burn_after_reading = 0; const int burn_after_reading = 0;
const int open_discussion = 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* paste_url = nullptr;
char* delete_token = nullptr; char* delete_token = nullptr;
std::cout << "[test] create_paste..." << std::endl; int rc = create_paste(server, plaintext_content, password, expiration, plaintext_format,
int rc = create_paste(server, content, password, expiration, format,
burn_after_reading, open_discussion, &paste_url, &delete_token); burn_after_reading, open_discussion, &paste_url, &delete_token);
if (rc != 0 || paste_url == nullptr || delete_token == nullptr) { 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 (paste_url) free_string(paste_url);
if (delete_token) free_string(delete_token); if (delete_token) free_string(delete_token);
return 1; return 1;
@ -68,7 +76,7 @@ int main() {
return 1; return 1;
} }
std::string fetched_str = fetched; 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; std::cout << "[test] delete_paste..." << std::endl;
rc = delete_paste(server, paste_id.c_str(), delete_token); rc = delete_paste(server, paste_id.c_str(), delete_token);
@ -80,11 +88,142 @@ int main() {
return 1; return 1;
} }
// cleanup // cleanup plaintext test
free_string(paste_url); free_string(paste_url);
free_string(delete_token); free_string(delete_token);
free_string(fetched); free_string(fetched);
std::cout << "[test] all API functions passed." << std::endl; // Test 2: Syntax highlighting format
std::cout << "[test] 2. Testing syntax highlighting format..." << std::endl;
const char* code_content = R"(
#include <iostream>
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; return 0;
} }