Script toggles for DNS over HTTPS on public Wi-Fi

DNS over HTTPS has been fun to try out. I’ve been using Cloudflare’s command line client/service/proxy configuration and working through some of the quirks with day to day use.

In a nutshell: A DNS proxy is setup on your local machine that makes DNS over HTTPS requests to Cloudflare’s and servers. Your configured DNS server is, and lookups are encrypted between your local machine and With this data encrypted, your service provider (home ISP, coffee shop, hotel, airport, etc…) does not see the domain name requests that are made.

This works great until you join a wireless network with some kind of captive portal requiring you to either login or accept some kind of terms. At that point, the network’s DNS is usually used to provide the address for that captive portal and no other network activity is allowed until that process completes. In many cases, this will not be compatible with a DNS over HTTPS configuration.

There are a handful of steps required for switching back and forth, which could be a pain if you’re frequently bouncing between locations.

  • Enable or disable the cloudflared service
  • Enable or disable the dnsmasq service (if using that to capture local .test lookups for example)
  • Change the DNS configuration to either or a default config to allow the network to serve its own DNS.

To handle this, I wrote a couple quick bash scripts that I can use to reduce my annoyance and toggle things back and forth.

The doh-enable script turns on cloudflared, turns on dnsmasq, and sets the local IP as a DNS server:

# doh-enable: enables the DNS over HTTPS config
sudo launchctl setenv TUNNEL_DNS_PORT 54
sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
sudo brew services start dnsmasq
networksetup -setdnsservers Wi-Fi

The doh-disable script turns off cloudflared, turns off dnsmasq, and empties the custom DNS server config to accept the network default:

# doh-disable: disables the DNS over HTTPS config
sudo launchctl unload /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
sudo brew services stop dnsmasq
networksetup -setdnsservers Wi-Fi empty

Now when I encounter a captive portal situation, all I need to do is type one command, sign in, then type another.

If you’re interested in trying out DNS over HTTPS, I found the Cloudflare documentation well written and this article helpful for getting dnsmasq running alongside it.

Restricted XML-RPC methods for WordPress mobile app support

For a long time I’ve had the path to xmlrpc.php blocked completely on a handful of sites so that I didn’t have to worry about Things. One thing that this messes with, if you don’t have Jetpack installed, is the WordPress mobile app. Without a connection, the mobile application relies on the XML-RPC API provided by your WordPress.

So I made a couple plugins.

First, the one you shouldn’t use in production, Log XML-RPC Requests, does exactly what it implies: logs incoming XML-RPC requests to a WordPress site as a custom post type.

I activated this on a site and then went screen by screen through the WordPress Android application to determine what XML-RPC methods were absolutely required in order for things to work.

That data was then used in the Restricted XML-RPC Methods plugin. This rejects any XML-RPC request that is not one of those required by the WordPress Android application. And pingbacks, because they’re underused and will always have a special place in my heart. 🙂

Any extra methods required by the WordPress IOS app are not enabled, but only because I don’t use an iPhone as my primary device. I’m happy to add those in if somebody passes me the list. It’s also possible that it just works!

Encoding serialized attributes for Gutenberg blocks in PHP

I stumbled backwards into this and didn’t see it discussed anywhere else yet, so here’s a path for anyone else finding themselves temporarily confused and internet searching.

Say you’ve created a custom block for the WordPress block editor, Gutenberg. In addition to regular content, there are one or more attributes associated with the block:

<!-- wp:jf/custom { "data":"Some data assigned as an attribute." } /-->

Now that you have the block in place, you want to migrate the meta from thousands of posts into this structure and inject it into the post content. This data can contain all sorts of characters, including quotes and HTML markup.

If you enter quotes and HTML markup directly into Gutenberg, the content is saved like so:

<!-- wp:jf/custom { "data":"Hey, a \u003Ca href=\u0022http:s\/\/\/\u0022\u003Elink to my website\u003C\/a\u003E." } /-->

This works because the Block API replaces a handful of special characters via regex after JSON.stringify()has done the initial translation.

WordPress does not have a PHP mirror of the Block API’s serializeAttributes(), but the existing functions for encoding JSON are really flexible. Per the documentation for json_encode(), which itself is used by wp_json_encode(), a handful of constants are available to apply as a bitmask to ensure specific characters are encoded in a specific way.

So we end up with this:

// String from non-Gutenberg data source.
$string = 'Hey, a <a href="">link to my website</a>.';

$attribute = wp_json_encode( $string, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE );

// And $attribute is now:
// "Hey, a \u003Ca href=\u0022https:\/\/\/\u0022\u003Elink to my website\u003C\/a\u003E."

// If storing with wp_insert_post(), etc...
$attribute = addslashes( $attribute );

$block = '<!-- wp:jf/custom { "data":' . $attribute . ' } /-->';

And the block string can now be stored as part of the post content.

Note: addslashes() is needed if storing the data with wp_insert_post(), etc… because those strip slashes and \u003Ca looks like a slash that needs to be stripped unless it itself is slashed.