1 CORS
n edited this page 2026-08-08 16:39:04 +02:00

To make CORS work for a self-hosted calendar like Baikal, that doesn't support it by itself, you can let your webserver handle the respective OPTIONS requests.

Caddy

I use Caddy and Baikal runs as a container via podman, so it is connected via reverse_proxy. My configuration looks like this:

dav.koehr.ing {
  route {
    @denied path_regexp ^/(\.ht|Core|Specific)
    respond @denied 404

    @preflight {
      method OPTIONS
      header Access-Control-Request-Method *
    }
    header @preflight {
      Access-Control-Allow-Origin "*"
      Access-Control-Allow-Methods "GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK"
      Access-Control-Allow-Headers "User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With"
      Access-Control-Max-Age "1728000"
    }
    respond @preflight 204

    redir /.well-known/carddav* /dav.php 308
    redir /.well-known/caldav* /dav.php 308

    @cors {
      header Origin *
    }
    header @cors {
      Access-Control-Allow-Origin "*"
      Access-Control-Allow-Methods "GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK"
      Access-Control-Allow-Headers "User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With"
      Access-Control-Expose-Headers "Etag,Preference-Applied"
    }

    reverse_proxy * localhost:10500
  }
}

It is important to keep everything inside the route block, otherwise Caddy internally sorts redir before the preflight rules.

Nginx

For your convenience, this is a copy of an Nginx config I found here. I didn't try it, though:

# Based on http://sabre.io/baikal/install/

# If you use different hostnames for Baikal and Infcloud,
# e.g. dav.mydomain.org and infcloud.mydomain.org, then
# replace "*" in "Access-Control-Allow-Origin" with your
# hostname where Infcloud runs, e.g. infcloud.mydomain.com.

server {
  listen 80;
  server_name _;

  root   /var/www/baikal/html;
  index index.php;

  rewrite ^/.well-known/caldav /dav.php redirect;
  rewrite ^/.well-known/carddav /dav.php redirect;

  charset utf-8;

  location ~ /(\.ht|Core|Specific) {
    deny all;
    return 404;
  }

  # Pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
  location ~ ^(.+\.php)(.*)$ {
    try_files $fastcgi_script_name =404;
    include        /etc/nginx/fastcgi_params;
    fastcgi_split_path_info  ^(.+\.php)(.*)$;
    fastcgi_pass   unix:/var/run/php/php7.4-fpm.sock;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO        $fastcgi_path_info;

    # Add CORS support, e.g. for InfCloud
    # See
    # - https://enable-cors.org/server_nginx.html
    # - https://www.inf-it.com/infcloud/readme.txt section 3
    # - https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/

    # cors_flags is 0 : Not a browser request, not a preflight request
    # cors_flags is 1 : Browser request, not a preflight request
    # cors_flags is 11: Preflight request sent by a browser
    set $cors_flags 0;
    if ($http_user_agent ~ "Mozilla") {
      set $cors_flags 1;
    }
    if ($request_method = 'OPTIONS') {
      set $cors_flags "${cors_flags}1";
    }

    # Browser preflight request: add CORS headers and don't forward to Baikal
    if ($cors_flags = 11) {
      add_header 'Access-Control-Allow-Origin' '*' always;
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK' always;
      add_header 'Access-Control-Allow-Headers' 'User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With' always;

      # Tell client that this pre-flight info is valid for 20 days
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Content-Length' 0;

      return 204;
    }

    # From browser, but not a preflight request: add CORS headers and forward to Baikal
    if ($cors_flags = 1) {
      add_header 'Access-Control-Allow-Origin' '*' always;
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PROPFIND, PROPPATCH, REPORT, PUT, MOVE, DELETE, LOCK, UNLOCK' always;
      add_header 'Access-Control-Allow-Headers' 'User-Agent,Authorization,Content-type,Depth,If-match,If-None-Match,Lock-Token,Timeout,Destination,Overwrite,Prefer,X-client,X-Requested-With' always;
      add_header 'Access-Control-Expose-Headers' 'Etag,Preference-Applied' always;
    }
  }
}