"""Sube (PUT) un VCARD a una coleccion CardDAV via HTTP Basic auth. Funcion impura: hace una peticion HTTP PUT. Construye el header `Authorization: Basic base64(user:pass)` a mano con stdlib. El nombre del recurso se deriva del UID saneado (`safe(uid) + '.vcf'`). Maneja errores sin lanzar: devuelve {status: 'ok', http_status: int} en exito o {status: 'error', error: str}. Solo usa stdlib (urllib, base64, re, ssl). """ import base64 import re import ssl import urllib.error import urllib.request _UNSAFE_RE = re.compile(r"[^A-Za-z0-9_.-]") def _basic_auth_header(username: str, password: str) -> str: raw = ("%s:%s" % (username, password)).encode("utf-8") return "Basic " + base64.b64encode(raw).decode("ascii") def _safe_resource_name(uid: str, ext: str) -> str: safe = _UNSAFE_RE.sub("_", uid)[:120] return safe + ext def _join_url(base_url: str, collection_path: str, resource: str) -> str: return base_url.rstrip("/") + "/" + collection_path.strip("/") + "/" + resource def carddav_put_vcard( base_url: str, username: str, password: str, collection_path: str, uid: str, vcard_text: str, *, timeout_s: float = 20.0, verify_tls: bool = True, ) -> dict: """Sube un VCARD a una coleccion CardDAV (PUT). Args: base_url: URL base del servidor DAV (p.ej. 'https://dav-x.example.com'). username: usuario para HTTP Basic auth. password: contrasena para HTTP Basic auth. collection_path: ruta de la coleccion CardDAV (p.ej. '/enmanuel/contacts/addressbook/'). uid: UID del contacto; se sanea para formar el nombre del recurso. vcard_text: texto completo del VCARD (BEGIN:VCARD..END:VCARD). timeout_s: timeout de la peticion en segundos. Default 20.0. verify_tls: si True (default) verifica el certificado TLS. No desactivar salvo en entornos de prueba controlados. Returns: dict. En exito: {status: 'ok', http_status: int, url: str}. En error (sin lanzar): {status: 'error', error: str, http_status: int|None}. """ resource = _safe_resource_name(uid, ".vcf") url = _join_url(base_url, collection_path, resource) body = vcard_text if not body.endswith("\r\n"): body = body.rstrip("\r\n") + "\r\n" data = body.encode("utf-8") headers = { "Authorization": _basic_auth_header(username, password), "Content-Type": "text/vcard; charset=utf-8", } req = urllib.request.Request(url, data=data, method="PUT", headers=headers) context = None if verify_tls else ssl._create_unverified_context() try: with urllib.request.urlopen(req, timeout=timeout_s, context=context) as resp: return {"status": "ok", "http_status": resp.status, "url": url} except urllib.error.HTTPError as e: return {"status": "error", "error": "http %s" % e.code, "http_status": e.code} except urllib.error.URLError as e: return {"status": "error", "error": str(e.reason), "http_status": None} except Exception as e: # noqa: BLE001 return {"status": "error", "error": str(e), "http_status": None}