v2.21.0 and try github actions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d385746
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,42 @@
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+jobs:
+  test:
+    name: test
+    runs-on: ${{matrix.os}}
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - ubuntu-latest
+        otp:
+          - "24.0.2"
+          - "23.3.1"
+          - "22.3.4.9"
+          - "21.3.8.17"
+          - "20.3.8.26"
+        include:
+          - os: ubuntu-18.04
+            otp: "21.2.7"
+          - os: ubuntu-18.04
+            otp: "19.3.6.13"
+          - os: ubuntu-18.04
+            otp: "18.3.4.11"
+          - os: ubuntu-18.04
+            otp: "16.b.2.basho10"
+    steps:
+      - uses: actions/checkout@v2.3.2
+      - run: |
+          VERSION=${{matrix.otp}}
+          RELEASE=$(lsb_release -cs)
+          DIR=$(mktemp -d)
+          pushd $DIR
+          FILE=esl-erlang_$VERSION-1~ubuntu~$RELEASE\_amd64.deb
+          wget https://packages.erlang-solutions.com/erlang/debian/pool/$FILE
+          sudo dpkg -i $FILE
+          popd
+          rm -r $DIR
+      - run: make test
diff --git a/.travis.yml b/.travis.yml
index b1cfa14..00984a8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,19 +2,8 @@
 language: erlang
 notifications:
   email: false
-otp_release:
-  - 23.0
-  - 22.0
-  - 21.3
-  - 21.2.3
-  - 21.1
-  - 21.0
-  - 20.0
-  - 19.0
 matrix:
   include:
-  - otp_release: 18.3
-    dist: trusty
   - otp_release: 17.5
     dist: trusty
   - otp_release: R16B03-1
diff --git a/CHANGES.md b/CHANGES.md
index 99fa525..65e171c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,10 @@
+Version 2.21.0 released 2021-06-06
+
+* Upgrade crypto functions to support OTP 23
+  https://github.com/mochi/mochiweb/pull/231
+* Switch from Travis to GitHub Actions for testing
+  https://github.com/mochi/mochiweb/pull/232
+
 Version 2.20.1 released 2020-02-03
 
 * Removed deprecated metadata from .app file
diff --git a/README.md b/README.md
index dcd96f8..e1e1040 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,8 @@
 
 Information about Rebar (Erlang build tool) is available at https://github.com/rebar/rebar
 
-MochiWeb is currently tested with Erlang/OTP R15B03 through 22.0.
+MochiWeb is currently tested with Erlang/OTP 18.3 through 24.0,
+but may still be compatible back to R15B-03.
 
 # OTP 21.2, 21.2.1, 21.2.2 warning
 
diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src
index f8d30b4..7b9578b 100644
--- a/src/mochiweb.app.src
+++ b/src/mochiweb.app.src
@@ -1,7 +1,7 @@
 %% This is generated from src/mochiweb.app.src
 {application, mochiweb,
  [{description, "MochiMedia Web Server"},
-  {vsn, "2.20.1"},
+  {vsn, "2.21.0"},
   {modules, []},
   {registered, []},
   {env, []},
diff --git a/src/mochiweb_util.erl b/src/mochiweb_util.erl
index dde2962..5af2b24 100644
--- a/src/mochiweb_util.erl
+++ b/src/mochiweb_util.erl
@@ -356,6 +356,12 @@
 urlsplit_query([C | Rest], Acc) ->
     urlsplit_query(Rest, [C | Acc]).
 
+extension(Name) ->
+    case filename:extension(Name) of
+        "" -> Name;
+        Ext -> Ext
+    end.
+
 %% @spec guess_mime(string()) -> string()
 %% @doc  Guess the mime type of a file by the extension of its filename.
 guess_mime(File) ->
@@ -363,7 +369,7 @@
         "crossdomain.xml" ->
             "text/x-cross-domain-policy";
         Name ->
-            case mochiweb_mime:from_extension(filename:extension(Name)) of
+            case mochiweb_mime:from_extension(extension(Name)) of
                 undefined ->
                     "text/plain";
                 Mime ->