Files
fn_registry/docs/capabilities/dav.md
T
egutierrez 1c8a86594f feat(dav): expand_rrule + dav_make_calendar para recurrencia y multi-calendario
Dos funciones nuevas del grupo de capacidad `dav`:
- expand_rrule_py_infra (pure): expande una RRULE iCalendar a las fechas de
  cada ocurrencia dentro de un rango [from, to]. Solo stdlib (datetime, re).
  Soporta FREQ DAILY/WEEKLY/MONTHLY/YEARLY, INTERVAL, COUNT, UNTIL, BYDAY. 9 tests.
- dav_make_calendar_py_infra (impure): crea una coleccion de calendario nueva
  via MKCALENDAR + PROPPATCH de nombre/color. Idempotente si ya existe. 11 tests.

Consumidas por la app osint_web (eventos recurrentes + creacion de agendas).
Pagina del grupo dav actualizada con ambas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:30:01 +02:00

5.5 KiB

dav — Cliente CardDAV/CalDAV (Python, solo stdlib)

Grupo de capacidad para operar un servidor CardDAV/CalDAV (Xandikos, git-backed, en el VPS magnus) desde Python sin dependencias externas. Cubre el flujo de migracion: partir un export de Google (un .vcf con N contactos, un .ics con N eventos) en recursos individuales y subirlos uno a uno por HTTP PUT con Basic auth. Tambien listar y descargar recursos para verificar o hacer backup.

Formaliza el flujo ad-hoc (heredocs) que migro 820 contactos + 98 eventos a Xandikos (regla function_growth_and_self_docs: una composicion repetida >2 veces se promueve a funciones/pipelines del registry).

Restriccion de diseno

Solo stdlib (urllib.request, re, hashlib, base64, ssl). Sin requests, caldav ni vobject. El header Authorization: Basic base64(user:pass) se construye a mano. verify_tls=True por defecto. Coherente con el grupo osint-passive (sin deps).

Funciones

ID Firma corta Que hace Purity
split_vcards_py_infra split_vcards(vcf_text) -> list Parte un .vcf en VCARDs individuales pure
split_vevents_to_vcalendars_py_infra split_vevents_to_vcalendars(ics_text, prodid?) -> list Parte un VCALENDAR con N VEVENT en N VCALENDARs autonomos (replica VTIMEZONE) pure
extract_or_make_uid_py_infra extract_or_make_uid(text, prefix?) -> str Extrae el UID: o sintetiza <prefix><md5[:16]> determinista pure
carddav_put_vcard_py_infra carddav_put_vcard(base_url, user, pw, coll, uid, vcard) -> dict PUT de un VCARD (.vcf, text/vcard) impure
caldav_put_event_py_infra caldav_put_event(base_url, user, pw, coll, uid, vcal) -> dict PUT de un VCALENDAR (.ics, text/calendar) impure
dav_list_resources_py_infra dav_list_resources(base_url, user, pw, coll) -> dict PROPFIND Depth:1 -> lista de {href, etag} impure
dav_get_resource_py_infra dav_get_resource(base_url, user, pw, href) -> dict GET de un recurso -> texto VCARD/VCALENDAR impure
dav_make_calendar_py_infra dav_make_calendar(base_url, user, pw, calendar_home, slug, name?, color?, desc?) -> dict MKCALENDAR + PROPPATCH: crea una coleccion de calendario (agenda) nueva impure
expand_rrule_py_infra expand_rrule(dtstart_ical, rrule, range_start, range_end, all_day?) -> list Expande una RRULE iCalendar a las fechas de cada ocurrencia dentro de un rango pure
import_vcf_to_carddav_py_pipelines import_vcf_to_carddav(vcf_path, base_url, user, pw, coll) -> dict Pipeline: .vcf -> split -> uid -> PUT por tarjeta impure
import_ics_to_caldav_py_pipelines import_ics_to_caldav(ics_path, base_url, user, pw, coll) -> dict Pipeline: .ics -> split -> uid -> PUT por evento impure

Sistema real (para los ejemplos)

  • Servidor: Xandikos en https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com, Basic auth, usuario enmanuel.
  • Password: pass dav/xandikos-enmanuel (primera linea). Resolver con pass_get_secret_py_infra, NUNCA hardcodear.
  • Principal: /enmanuel/. Colecciones:
    • CardDAV: /enmanuel/contacts/addressbook/
    • CalDAV: /enmanuel/calendars/calendar/

Ejemplo canonico end-to-end

Importar un .vcf exportado de Google a Xandikos, leyendo la password de pass:

import sys
sys.path.insert(0, "python/functions")
from infra.pass_get_secret import pass_get_secret
from pipelines.import_vcf_to_carddav import import_vcf_to_carddav

BASE = "https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com"
pw = pass_get_secret("dav/xandikos-enmanuel")["value"]  # NO logear

summary = import_vcf_to_carddav(
    vcf_path="/home/enmanuel/Descargas/contacts.vcf",
    base_url=BASE,
    username="enmanuel",
    password=pw,
    collection_path="/enmanuel/contacts/addressbook/",
)
print(summary["ok"], summary["fail"], summary["total"])  # 820 0 820

Verificar el resultado listando la coleccion:

from infra.dav_list_resources import dav_list_resources
res = dav_list_resources(BASE, "enmanuel", pw, "/enmanuel/contacts/addressbook/")
print(res["status"], len(res["resources"]))  # ok 820

El calendario es analogo con import_ics_to_caldav + /enmanuel/calendars/calendar/.

Desde la CLI del registry (resuelve la pass como variable, no la pongas en claro):

PW=$(pass show dav/xandikos-enmanuel | head -n1)
./fn run import_vcf_to_carddav /home/enmanuel/Descargas/contacts.vcf \
  https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com \
  enmanuel "$PW" /enmanuel/contacts/addressbook/

Fronteras

  • No descubre el principal ni las colecciones: hay que conocer los paths (/enmanuel/contacts/addressbook/, etc.). No implementa current-user-principal ni addressbook-home-set discovery.
  • No hace sync incremental real: dav_list_resources devuelve etags pero no hay logica de diff/merge. Re-importar es idempotente por UID (sobrescribe), no incremental.
  • No parsea campos VCARD/VEVENT: trata cada componente como texto opaco. Para transformar contenido (renombrar, deduplicar por nombre) usa otra herramienta.
  • Solo VEVENT en calendario: VTODO/VJOURNAL se ignoran al partir el .ics.
  • Escrituras irreversibles: los PUT sobrescriben en el servidor. Idempotente por UID pero no hay confirmacion previa; valida el .vcf/.ics antes de importar.

Prerequisitos

  • pass configurado con la entrada dav/xandikos-enmanuel.
  • Conectividad TLS al endpoint publico (verify_tls=True).
  • Python del registry: python/.venv/bin/python3.