--- name: carddav_put_vcard kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "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" description: "Sube (HTTP PUT) un VCARD a una coleccion CardDAV con HTTP Basic auth. Construye el header Authorization: Basic base64(user:pass) a mano con stdlib. El nombre del recurso se deriva del UID saneado (safe(uid)+'.vcf'). verify_tls=True por defecto. Idempotente por UID: re-subir el mismo UID sobrescribe el recurso. Maneja errores sin lanzar (HTTPError/URLError -> {status:'error'}). Solo stdlib (urllib, base64, re, ssl). Probado contra Xandikos." tags: [dav, carddav, vcard, http, put, contacts, infra, upload] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [base64, re, ssl, urllib.error, urllib.request] params: - name: base_url desc: "URL base del servidor DAV (p.ej. 'https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com')." - name: username desc: "usuario para HTTP Basic auth (p.ej. 'enmanuel')." - name: password desc: "contrasena para HTTP Basic auth. Resolver desde pass con pass_get_secret, nunca hardcodear." - name: collection_path desc: "ruta de la coleccion CardDAV (p.ej. '/enmanuel/contacts/addressbook/')." - name: uid desc: "UID del contacto; se sanea ([^A-Za-z0-9_.-]->_ , max 120 chars) para formar el nombre del recurso .vcf." - name: vcard_text desc: "texto completo del VCARD (BEGIN:VCARD..END:VCARD). Se asegura terminacion en CRLF." - name: timeout_s desc: "timeout de la peticion HTTP en segundos. Default 20.0." - name: verify_tls desc: "si True (default) verifica el certificado TLS. No desactivar salvo entorno de prueba." output: "dict. En exito: {status:'ok', http_status:int, url:str}. En error (sin lanzar): {status:'error', error:str, http_status:int|None}. http_status es el codigo HTTP devuelto por el servidor (201 created / 204 no content tipico en CardDAV)." tested: true tests: - "test_construye_request_put_con_headers_correctos" - "test_url_se_forma_con_uid_saneado" - "test_content_type_es_text_vcard" - "test_basic_auth_header_correcto" - "test_httperror_devuelve_status_error" test_file_path: "python/functions/infra/carddav_put_vcard_test.py" file_path: "python/functions/infra/carddav_put_vcard.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from infra.pass_get_secret import pass_get_secret from infra.carddav_put_vcard import carddav_put_vcard pw = pass_get_secret("dav/xandikos-enmanuel")["value"] # NO logear res = carddav_put_vcard( base_url="https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com", username="enmanuel", password=pw, collection_path="/enmanuel/contacts/addressbook/", uid="abc-123@google.com", vcard_text="BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Ada Lovelace\r\nUID:abc-123@google.com\r\nEND:VCARD\r\n", ) print(res) # {"status": "ok", "http_status": 201, "url": ".../abc-123_google.com.vcf"} ``` ## Cuando usarla Cuando quieres subir un contacto individual a Xandikos (u otro servidor CardDAV) por HTTP. Es la primitiva de escritura del grupo `dav`; el pipeline `import_vcf_to_carddav` la invoca por cada tarjeta de un .vcf. Antes de llamarla, resuelve el UID con `extract_or_make_uid` y la password con `pass_get_secret`. ## Gotchas - Hace una escritura remota real: re-subir el mismo UID SOBRESCRIBE el recurso en el servidor (idempotente, no acumula duplicados — esa es la intencion). - La contrasena va en el header Basic en claro sobre TLS; nunca hardcodear, leer de `pass`. La funcion no logea la password. - `verify_tls=False` solo para entornos de prueba; deja un agujero MITM. - El servidor puede rechazar (4xx) si el path de la coleccion no existe o el UID del nombre del recurso no coincide con el UID dentro del VCARD: asegurate de que el mismo UID se usa para el nombre del archivo y para el campo UID:. - Devuelve dict (status/http_status/error), NO un int crudo: asi captura errores HTTP/red sin lanzar. Consulta `res["http_status"]` para el codigo.