Metadata-Version: 2.4
Name: icalendar-anonymizer
Version: 0.1.0
Summary: Strip personal data from iCalendar files while preserving technical properties for bug reproduction
Project-URL: Homepage, https://github.com/mergecal/icalendar-anonymizer
Project-URL: Repository, https://github.com/mergecal/icalendar-anonymizer
Project-URL: Issues, https://github.com/mergecal/icalendar-anonymizer/issues
Project-URL: Documentation, https://github.com/mergecal/icalendar-anonymizer#readme
Project-URL: Changelog, https://github.com/mergecal/icalendar-anonymizer/blob/main/CHANGES.rst
Project-URL: source_archive, https://github.com/mergecal/icalendar-anonymizer/archive/66f1769da2a5ed9af17fe291fdcc2ae7c99e84f1.zip
Author-email: Nicco Kunzmann <niccokunzmann@rambler.ru>, Sashank Bhamidi <hello@sashank.wiki>, Abe Hanoka <abe@habet.dev>
Maintainer-email: Nicco Kunzmann <niccokunzmann@rambler.ru>, Sashank Bhamidi <hello@sashank.wiki>, Abe Hanoka <abe@habet.dev>
License-Expression: AGPL-3.0-or-later
License-File: LICENSE
Keywords: anonymizer,calendar,icalendar,ics,privacy,vcalendar
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Office/Business :: Scheduling
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: icalendar>=6.3.0
Provides-Extra: all
Requires-Dist: build>=1.3; extra == 'all'
Requires-Dist: click>=8.3; extra == 'all'
Requires-Dist: commitizen>=4.10; extra == 'all'
Requires-Dist: coverage>=7.11; extra == 'all'
Requires-Dist: fastapi>=0.121.0; extra == 'all'
Requires-Dist: pre-commit>=4.4; extra == 'all'
Requires-Dist: pytest-cov>=6.0; extra == 'all'
Requires-Dist: pytest>=9.0; extra == 'all'
Requires-Dist: reuse>=6.2; extra == 'all'
Requires-Dist: ruff>=0.14.0; extra == 'all'
Requires-Dist: sphinx>=8.2; extra == 'all'
Requires-Dist: twine>=6.2; extra == 'all'
Requires-Dist: uvicorn[standard]>=0.38.0; extra == 'all'
Provides-Extra: cli
Requires-Dist: click>=8.3; extra == 'cli'
Provides-Extra: dev
Requires-Dist: build>=1.3; extra == 'dev'
Requires-Dist: commitizen>=4.10; extra == 'dev'
Requires-Dist: coverage>=7.11; extra == 'dev'
Requires-Dist: pre-commit>=4.4; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest>=9.0; extra == 'dev'
Requires-Dist: reuse>=6.2; extra == 'dev'
Requires-Dist: ruff>=0.14.0; extra == 'dev'
Requires-Dist: twine>=6.2; extra == 'dev'
Provides-Extra: doc
Requires-Dist: sphinx>=8.2; extra == 'doc'
Provides-Extra: test
Requires-Dist: coverage>=7.11; extra == 'test'
Requires-Dist: hypothesis>=6.147; extra == 'test'
Requires-Dist: pytest-cov>=6.0; extra == 'test'
Requires-Dist: pytest>=9.0; extra == 'test'
Provides-Extra: web
Requires-Dist: fastapi>=0.121.0; extra == 'web'
Requires-Dist: uvicorn[standard]>=0.38.0; extra == 'web'
Description-Content-Type: text/markdown

<!--- SPDX-FileCopyrightText: 2025 icalendar-anonymizer contributors -->
<!--- SPDX-License-Identifier: AGPL-3.0-or-later -->

# icalendar-anonymizer

Strip personal data from iCalendar files while preserving technical properties for bug reproduction.

Calendar bugs are hard to reproduce without actual calendar data, but people can't share their calendars publicly due to privacy concerns. This tool uses hash-based anonymization to remove sensitive information (names, emails, locations, descriptions) while keeping all date/time, recurrence, and timezone data intact.

[![Tests](https://github.com/mergecal/icalendar-anonymizer/actions/workflows/tests.yml/badge.svg)](https://github.com/mergecal/icalendar-anonymizer/actions/workflows/tests.yml)
[![codecov](https://codecov.io/gh/mergecal/icalendar-anonymizer/graph/badge.svg?token=6tcJpy0th3)](https://codecov.io/gh/mergecal/icalendar-anonymizer)
[![PyPI version](https://img.shields.io/pypi/v/icalendar-anonymizer.svg)](https://pypi.org/project/icalendar-anonymizer/)
[![Python versions](https://img.shields.io/pypi/pyversions/icalendar-anonymizer.svg)](https://pypi.org/project/icalendar-anonymizer/)
[![Docker pulls](https://img.shields.io/docker/pulls/sashankbhamidi/icalendar-anonymizer.svg)](https://hub.docker.com/r/sashankbhamidi/icalendar-anonymizer)
[![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL%203.0--or--later-blue.svg)](LICENSE)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

## Installation

```bash
# Library only
pip install icalendar-anonymizer

# With CLI tool
pip install icalendar-anonymizer[cli]

# With web service
pip install icalendar-anonymizer[web]

# Everything
pip install icalendar-anonymizer[all]
```

**Docker:**

```bash
docker-compose up -d
```

**From source:**

```bash
git clone https://github.com/mergecal/icalendar-anonymizer.git
cd icalendar-anonymizer
pip install -e ".[dev]"
```

## Usage

### Python API

```python
from icalendar import Calendar
from icalendar_anonymizer import anonymize

# Load calendar
with open('calendar.ics', 'rb') as f:
    cal = Calendar.from_ical(f.read())

# Anonymize
anonymized_cal = anonymize(cal)

# Preserve specific properties (e.g., CATEGORIES for bug reproduction)
anonymized_cal = anonymize(cal, preserve={"CATEGORIES", "LOCATION"})

# Deterministic output with fixed salt
salt = b"reproducible-salt-for-testing"
anonymized_cal = anonymize(cal, salt=salt)

# Save
with open('anonymized.ics', 'wb') as f:
    f.write(anonymized_cal.to_ical())
```

### Command Line

```bash
# Basic usage
icalendar-anonymize input.ics -o output.ics

# Shorter alias
ican input.ics -o output.ics

# Unix-style piping
cat calendar.ics | icalendar-anonymize > anonymized.ics

# Stdout by default
icalendar-anonymize calendar.ics
```

**Options:**
- `-i, --input FILE` - Input file (default: stdin)
- `-o, --output FILE` - Output file (default: stdout)
- `-v, --verbose` - Show processing details
- `--version` - Print version

### Web Service

```bash
# POST with ICS content
curl -X POST https://anonymizer.example.com/anonymize \
     -H "Content-Type: application/json" \
     -d '{"ics": "BEGIN:VCALENDAR..."}'

# Upload ICS file
curl -X POST https://anonymizer.example.com/upload \
     -F "file=@calendar.ics"

# Fetch and anonymize URL
curl "https://anonymizer.example.com/fetch?url=https://example.com/cal.ics"
```

FastAPI provides interactive docs at `/docs` for testing.

**Self-hosting:**

```bash
docker-compose up -d
```

Runs on port 8000 by default. Includes SSRF protection (blocks localhost and private IPs), 10-second timeout, and 10MB max file size.

## How It Works

**Hash-based anonymization** - Same input always produces the same output, preserving patterns for bug analysis.

**Structure preservation** - A 10-word summary stays 10 words. Emails still look like emails.

**What gets anonymized:**
- SUMMARY, DESCRIPTION, LOCATION
- ATTENDEE, ORGANIZER (including CN fields)
- COMMENT, CONTACT
- Unknown/X- properties (safe by default)

**What stays intact:**
- DTSTART, DTEND, DUE, DURATION, DTSTAMP
- RRULE, RDATE, EXDATE
- VTIMEZONE, TZID, TZOFFSETFROM, TZOFFSETTO
- UID (hashed but unique)
- SEQUENCE, STATUS, TRANSP, CLASS, PRIORITY

**Custom preservation:**
Use `preserve` parameter to keep specific properties:
```python
anonymize(cal, preserve={"CATEGORIES", "COMMENT"})
```
User responsibility to ensure preserved properties don't contain sensitive data.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, commit format, and testing requirements.

## License

AGPL-3.0-or-later. See [LICENSE](LICENSE).
