# A heavily customized VCL to support WordPress
# Some items of note:
# Supports https
# Supports admin cookies for wp-admin
# Caches everything
# Support for custom error html page
vcl 4.0;
import directors;
import std;

# Assumed 'wordpress' host, this can be docker servicename
backend default {
        .host = "127.0.0.1";
        .port = "8080";
        .connect_timeout = 1s;
        .first_byte_timeout = 30s;
        .between_bytes_timeout = 5s;
        .max_connections = 100;
}

acl purge {
        "localhost";
        "127.0.0.1";
}


sub vcl_recv {

        if (req.url ~ "phpmyadmin") {
                return (pass);
        }

        if (req.url ~ "/wp-(login|admin)") {
                return (pass);
        }

        # pass search URL
        if ( req.url ~ "^/\?s" ) {
                return (pass);
        }
        # pass search URL
        if ( req.url ~ "/search/" ) {
                return (pass);
        }

        # Only a single backend
        set req.backend_hint= default;

        # Setting http headers for backend
        set req.http.X-Forwarded-For = client.ip;
        set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

        # Unset headers that might cause us to cache duplicate infos
        unset req.http.Accept-Language;
        # Normalize Vary Header to keep different cache versions for desktop and mobile;
        if (req.http.User-Agent ~ "(Mobile|Android|iPhone|iPad)") {
                set req.http.User-Agent = "mobile";
        } else {
                set req.http.User-Agent = "desktop";
        }

        if (req.http.Authorization || req.method == "POST") {
                return (pass);
        }

        # Allow purge from allowed IPs only
        if (req.method == "PURGE") {
                if (!client.ip ~ purge) {
                        # return(synth(405,"Not allowed."));
                }
                return (purge);
        }

        # pass wp-admin urls
        if (req.url ~ "(wp-login|wp-admin|login|logout)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") {
                return (pass);
        }

        # drop cookies and params from static assets
        if (req.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") {
                unset req.http.cookie;
                set req.url = regsub(req.url, "\?.*$", "");
        }

        # drop tracking params
        if (req.url ~ "\?(utm_(campaign|medium|source|term)|adParams|client|cx|eid|fbid|feed|ref(id|src)?|v(er|iew))=") {
                set req.url = regsub(req.url, "\?.*$", "");
        }

        # Remove the "has_js" cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
        # Remove any Google Analytics based cookies
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
        # Remove the Quant Capital cookies (added by some plugin, all __qca)
        set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
        # Remove the wp-settings-1 cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-=[^;]+(; )?", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");
        # Remove the wp-settings-time-1 cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-=[^;]+(; )?", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");
        # Remove the wp test cookie
        set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
        # Are there cookies left with only spaces or that are empty?
        if (req.http.cookie ~ "^ *$") {
                unset req.http.cookie;
        }

        # pass wp-admin cookies
        if (req.http.cookie) {
                if (req.http.cookie ~ "(wordpress|wordpress_)") {
                        return(pass);
                } else {
                        unset req.http.cookie;
                }
        }
}

sub vcl_backend_response {
# retry a few times if backend is down
        if (beresp.status == 503 && bereq.retries < 3 ) {
                return(retry);
        }

        # Set TTL Explicitly if ignored from server
        if (beresp.ttl < 120s) {
                set beresp.ttl = 120s;
                unset beresp.http.Cache-Control;
        }

        if (beresp.status == 302){
                set beresp.http.X-Cacheable = "NO: 302";
                set beresp.uncacheable = true;
                return (deliver);
        }

        # Remove setcookies for static resources
        if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") {
                unset beresp.http.set-cookie;
                unset beresp.http.cookie;
        }

        # unset set-cookies from backendresponse if url is not for login users
        if (!(bereq.url ~ "(wp-login|wp-admin|login|logout)"))  {
                set beresp.http.X-UnsetCookies = "TRUE";
                unset beresp.http.set-cookie;
        }

        if (bereq.http.Cookie ~ "(wordpress|wordpress_logged_in)") {
                # if we get a session cookie...caching is a no-go
                set beresp.http.X-Cacheable = "NO:Got Session";
                set beresp.uncacheable = true;
                set beresp.ttl = 0s;
                return (deliver);

        } elsif (beresp.http.set-cookie) {
                # You don't wish to cache content for logged in users
                set beresp.http.X-Cacheable = "NO:Set-Cookie";
                set beresp.uncacheable = true;
                set beresp.ttl = 0s;
                return (deliver);

        } elsif (beresp.http.Cache-Control ~ "private") {
                # You are respecting the Cache-Control=private header from the backend
                set beresp.http.X-Cacheable = "NO:Cache-Control=private";
                set beresp.uncacheable = true;
                set beresp.ttl = 0s;
                return (deliver);

        } else {
                # Varnish determined the object was cacheable
                set beresp.http.X-Cacheable = "YES";

                # Remove Expires from backend, it's not long enough
                unset beresp.http.expires;

                # Set the clients TTL on this object
                set beresp.http.cache-control = "max-age=900";

                # Set how long Varnish will keep it
                set beresp.ttl = 365d;

                # marker for vcl_deliver to reset Age:
                set beresp.http.magicmarker = "1";
        }

        # setting ttl to 1h for backendresponse if url is not for login users
        if (!(bereq.url ~ "(wp-login|wp-admin|login|logout)"))  {
                set beresp.ttl = 1h;
        }

        # long ttl for assets
        if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") {
                set beresp.http.cache-control = "max-age=604800";
                set beresp.ttl = 365d;
        }

        set beresp.grace = 1h;
}

sub vcl_hash {
        # If the client supports compression, keep that in a different cache
        if (req.http.Accept-Encoding) {
                hash_data(req.http.Accept-Encoding);
        }
}

sub vcl_backend_error {
        # display custom error page if backend down
        if (beresp.status == 503 && bereq.retries == 3) {
                synthetic(std.fileread("/path/to/503.html"));
                return(deliver);
        }
 }

sub vcl_synth {
        # redirect for http
        if (resp.status == 750) {
                set resp.status = 301;
                set resp.http.Location = req.http.x-redir;
                return(deliver);
        }
        # display custom error page if backend down
        if (resp.status == 503) {
                synthetic(std.fileread("/path/to/503.html"));
                return(deliver);
        }
}

sub vcl_deliver {
        # oh backend is down
        if (resp.status == 503) {
                return(restart);
        }
        if (resp.http.magicmarker) {
                # Remove the magic marker
                unset resp.http.magicmarker;
                # By definition we have a fresh object
                set resp.http.age = "0";
        }
        if (obj.hits > 0) {
                set resp.http.X-Cache = "cached";
        } else {
                set resp.http.X-Cache = "uncached";
        }
        set resp.http.Access-Control-Allow-Origin = "*";
}

sub vcl_hit {
        if (req.method == "PURGE") {
                return(synth(200,"OK"));
        }
}

sub vcl_miss {
        if (req.method == "PURGE") {
                return(synth(404,"Not cached"));
        }
}

sub vcl_purge {
        return (synth(200, "Purged"));
}