{"record":{"id":"6ecf565fc36582f8","repo":"monicahq/monica","slug":"server-is-not-compliant-with-rfc2518-section-15-1","errorCode":null,"errorMessage":"server is not compliant with rfc2518 section 15.1, or rfc6352 section 6.1","messagePattern":"server is not compliant with rfc2518 section 15\\.1, or rfc6352 section 6\\.1","errorType":"exception","errorClass":"DavServerNotCompliantException","httpStatus":null,"severity":"error","filePath":"app/Domains/Contact/DavClient/Services/Utils/AddressBookGetter.php","lineNumber":155,"sourceCode":"        return $addressBook !== null\n            ? $this->client->path($addressBook)\n            : null;\n    }\n\n    /**\n     * Check options of the server.\n     *\n     * @see https://datatracker.ietf.org/doc/html/rfc2518#section-15\n     * @see https://datatracker.ietf.org/doc/html/rfc6352#section-6.1\n     *\n     * @throws DavServerNotCompliantException\n     */\n    private function checkOptions(bool $addressbook = false, string $url = ''): void\n    {\n        $options = $this->client->options($url);\n\n        if (! in_array('1', $options) || ! in_array('3', $options) || ($addressbook && ! in_array('addressbook', $options))) {\n            throw new DavServerNotCompliantException('server is not compliant with rfc2518 section 15.1, or rfc6352 section 6.1');\n        }\n    }\n\n    /**\n     * Get principal name.\n     *\n     * @see https://datatracker.ietf.org/doc/html/rfc5397#section-3\n     *\n     * @throws DavServerNotCompliantException\n     */\n    private function getCurrentUserPrincipal(string $uri = ''): string\n    {\n        $prop = $this->client->getProperty('{DAV:}current-user-principal', $uri);\n\n        if (is_null($prop) || empty($prop)) {\n            throw new DavServerNotCompliantException('Server does not support rfc 5397 section 3 (DAV:current-user-principal)');\n        } elseif (is_string($prop)) {\n            return $prop;","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/monicahq/monica/blob/e08e91734170b6bbd582cb578532c3948196124e/app/Domains/Contact/DavClient/Services/Utils/AddressBookGetter.php#L137-L173","documentation":"After an address book URL is found, checkOptions() sends an OPTIONS request and parses the DAV response header into compliance tokens. Monica requires class '1' and class '3', plus the 'addressbook' token when subscribing (RFC 2518 section 15.1 classes, RFC 6352 section 6.1 CardDAV compliance). If any token is missing from the DAV header the server is deemed not CardDAV-capable and DavServerNotCompliantException is thrown. Google endpoints (googleapis.com) are explicitly exempted from this check.","triggerScenarios":"Subscribing against a server whose OPTIONS DAV header lacks '1', '3' or 'addressbook': a plain WebDAV server (class 1 only), a CalDAV server without CardDAV support, a reverse proxy that strips the DAV header, or a server where the OPTIONS method is blocked so no DAV header comes back.","commonSituations":"nginx/Apache in front of the CardDAV server not passing the DAV header, pointing at a generic WebDAV mount or file server instead of a CardDAV server, or firewall/server rules that disable OPTIONS.","solutions":["Check the header yourself: curl -i -X OPTIONS <addressbook-url> — the DAV header must include 1, 3 and addressbook (e.g. 'DAV: 1, 2, 3, access-control, addressbook')","If a proxy sits in front, configure it to forward the DAV header (nginx: proxy_pass_header Dav; or stop dropping unknown headers)","Point Monica at a real CardDAV server (Nextcloud, Baikcal/radicale/sabre-dav based stacks)","Ensure no firewall or server rule blocks the OPTIONS method"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Preflight: assert the server advertises CardDAV compliance in its DAV header\n$response = Http::withBasicAuth($username, $password)->send('OPTIONS', $addressBookUrl);\n$davHeader = (string) $response->header('DAV');\n$tokens = array_map('trim', explode(',', $davHeader));\n\nif (! in_array('1', $tokens, true) || ! in_array('3', $tokens, true) || ! in_array('addressbook', $tokens, true)) {\n    throw ValidationException::withMessages([\n        'base_uri' => 'Server does not advertise CardDAV (DAV header: '.$davHeader.').',\n    ]);\n}","typeGuard":null,"tryCatchPattern":"use App\\Domains\\Contact\\DavClient\\Services\\Utils\\Dav\\DavServerNotCompliantException;\n\ntry {\n    app(CreateAddressBookSubscription::class)->execute($data);\n} catch (DavServerNotCompliantException $e) {\n    // not fixable from Monica: point at a real CardDAV server or fix the proxy\n    report($e);\n    throw ValidationException::withMessages(['base_uri' => $e->getMessage()]);\n}","preventionTips":["Smoke-test every new CardDAV backend with curl -i -X OPTIONS and read the DAV header","Configure reverse proxies to forward the DAV header and allow OPTIONS","Do not point subscriptions at plain WebDAV or CalDAV-only endpoints","Keep a checklist of supported servers (sabre/dav based: Nextcloud, Baikal, radicale)"],"tags":["carddav","dav","options","compliance","reverse-proxy"],"backgroundTag":"carddav-server-compliance","analyzedSha":"e08e91734170b6bbd582cb578532c3948196124e","analyzedAt":"2026-08-17T01:36:49.014Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}