diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,5 @@
+# Change Log
+
+## 1.0.0
+
+* Initial FOSS release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2020 Michael Williams
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# Zenacy Unicode
+
+[![hackage-shield][]][hackage-version]
+[![stackage-shield][]][stackage-version]
+[![linux-shield][]][linux-build]
+[![packdeps-shield][]][packdeps]
+
+Zenacy Unicode includes tools for checking byte order marks (BOM) and
+cleaning data to remove invalid bytes.  These tools can help ensure that
+data pulled from the web can be parsed and converted to text.
+
+The following is an example of converting dubious data to a text.
+
+```haskell
+textDecode :: ByteString -> Text
+textDecode b =
+  case bomStrip b of
+    (Nothing, s)           -> T.decodeUtf8 $ unicodeCleanUTF8 s -- Assume UTF8
+    (Just BOM_UTF8, s)     -> T.decodeUtf8 $ unicodeCleanUTF8 s
+    (Just BOM_UTF16_BE, s) -> T.decodeUtf16BE s
+    (Just BOM_UTF16_LE, s) -> T.decodeUtf16LE s
+    (Just BOM_UTF32_BE, s) -> T.decodeUtf32BE s
+    (Just BOM_UTF32_LE, s) -> T.decodeUtf32LE s
+```
+
+[hackage-shield]: https://img.shields.io/hackage/v/zenacy-unicode.svg?label=Hackage
+[hackage-version]: https://hackage.haskell.org/package/zenacy-unicode
+[stackage-shield]: https://www.stackage.org/package/zenacy-unicode/badge/nightly?label=Stackage
+[stackage-version]: https://www.stackage.org/package/zenacy-unicode
+[linux-shield]: https://img.shields.io/travis/com/mlcfp/zenacy-unicode?label=Linux%20build
+[linux-build]: https://travis-ci.org/mlcfp/zenacy-unicode
+[packdeps-shield]: https://img.shields.io/hackage-deps/v/zenacy-unicode.svg?maxAge=3600
+[packdeps]: http://packdeps.haskellers.com/feed?needle=zenacy-unicode
+
diff --git a/src/Zenacy/Unicode.hs b/src/Zenacy/Unicode.hs
new file mode 100644
--- /dev/null
+++ b/src/Zenacy/Unicode.hs
@@ -0,0 +1,182 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Tools to check and prepare data to be parsed as valid unicode.
+--
+-- The following is an example of converting dubious data to a text.
+--
+-- > textDecode :: ByteString -> Text
+-- > textDecode b =
+-- >   case bomStrip b of
+-- >     (Nothing, s)           -> T.decodeUtf8 $ unicodeCleanUTF8 s -- Assume UTF8
+-- >     (Just BOM_UTF8, s)     -> T.decodeUtf8 $ unicodeCleanUTF8 s
+-- >     (Just BOM_UTF16_BE, s) -> T.decodeUtf16BE s
+-- >     (Just BOM_UTF16_LE, s) -> T.decodeUtf16LE s
+-- >     (Just BOM_UTF32_BE, s) -> T.decodeUtf32BE s
+-- >     (Just BOM_UTF32_LE, s) -> T.decodeUtf32LE s
+module Zenacy.Unicode
+  ( BOM(..)
+  , bomStrings
+  , bomStrip
+  , unicodeCleanUTF8
+  ) where
+
+import Foreign
+  ( castPtr
+  , withForeignPtr
+  )
+import Control.Monad.ST
+  ( ST
+  , runST
+  )
+import Data.STRef
+  ( STRef
+  , newSTRef
+  , readSTRef
+  , writeSTRef
+  )
+import Data.ByteString
+  ( ByteString
+  )
+import qualified Data.ByteString as S
+  ( index
+  , length
+  , null
+  , packCStringLen
+  , pack
+  , stripPrefix
+  )
+import Data.Vector.Storable.Mutable
+  ( MVector(..)
+  )
+import qualified Data.Vector.Storable.Mutable as U
+  ( new
+  , length
+  , write
+  , grow
+  , unsafeToForeignPtr0
+  )
+import Data.Word
+  ( Word8
+  )
+import System.IO.Unsafe
+  ( unsafePerformIO
+  )
+
+-- | Defines the unicode byte order mark.
+data BOM
+  = BOM_UTF8
+  | BOM_UTF16_BE
+  | BOM_UTF16_LE
+  | BOM_UTF32_BE
+  | BOM_UTF32_LE
+    deriving (Eq, Ord, Show)
+
+-- | Defines the byte order mark signatures.
+bomStrings :: [(BOM,ByteString)]
+bomStrings =
+  [ ( BOM_UTF8,     S.pack [ 0xEF, 0xBB, 0xBF ] )
+  , ( BOM_UTF32_BE, S.pack [ 0x00, 0x00, 0xFE, 0xFF ] )
+  , ( BOM_UTF32_LE, S.pack [ 0xFF, 0xFE, 0x00, 0x00 ] )
+  -- The 16 bit codes need to be checked after the 32 bit codes,
+  -- because the prefixes are similar.
+  , ( BOM_UTF16_BE, S.pack [ 0xFE, 0xFF ] )
+  , ( BOM_UTF16_LE, S.pack [ 0xFF, 0xFE ] )
+  ]
+
+-- | Remove the BOM from the start of a string.
+bomStrip :: ByteString -> (Maybe BOM, ByteString)
+bomStrip x =
+  go bomStrings
+  where
+    go [] =
+      (Nothing, x)
+    go ((b,s):bs) =
+      case S.stripPrefix s x of
+        Just x' -> (Just b, x')
+        Nothing -> go bs
+
+-- | Removes bad characters and nulls from a UTF8 byte string.
+unicodeCleanUTF8 :: ByteString -> ByteString
+unicodeCleanUTF8 x =
+  runST $ do
+    v <- U.new 100
+    go 0 0 v
+  where
+    go i j u
+      | i == S.length x = do
+          dataString u j
+      | otherwise = do
+          v <- if j + 3 < U.length u
+                  then pure u
+                  else U.grow u $ U.length u
+
+          let c0 = S.index x (i + 0)
+              c1 = S.index x (i + 1)
+              c2 = S.index x (i + 2)
+              c3 = S.index x (i + 3)
+
+          if | (c0 >= 0x01 && c0 <= 0x7F) -> do
+                 U.write v (j + 0) c0
+                 go (i + 1) (j + 1) v
+
+             | (c0 >= 0xC0 && c0 <= 0xDF) -> do
+                 if | i + 1 < S.length x &&
+                      (c1 >= 0x80 && c1 <= 0xBF) -> do
+                        U.write v (j + 0) c0
+                        U.write v (j + 1) c1
+                        go (i + 2) (j + 2) v
+                    | otherwise -> do
+                        rep (i + 1) j v
+
+             | (c0 >= 0xE0 && c0 <= 0xEF) -> do
+                 if | i + 2 < S.length x &&
+                      (c1 >= 0x80 && c1 <= 0xBF) &&
+                      (c2 >= 0x80 && c2 <= 0xBF) -> do
+                        U.write v (j + 0) c0
+                        U.write v (j + 1) c1
+                        U.write v (j + 2) c2
+                        go (i + 3) (j + 3) v
+                    | (c1 >= 0x80 && c1 <= 0xBF) -> do
+                        rep (i + 2) j v
+                    | otherwise ->
+                        rep (i + 1) j v
+
+             | (c0 >= 0xF0 && c0 <= 0xF7) -> do
+                 if | i + 3 < S.length x &&
+                      (c1 >= 0x80 && c1 <= 0xBF) &&
+                      (c2 >= 0x80 && c2 <= 0xBF) &&
+                      (c3 >= 0x80 && c3 <= 0xBF) -> do
+                        U.write v (j + 0) c0
+                        U.write v (j + 1) c1
+                        U.write v (j + 2) c2
+                        U.write v (j + 3) c3
+                        go (i + 4) (j + 4) v
+                    | (c1 >= 0x80 && c1 <= 0xBF) &&
+                      (c2 >= 0x80 && c2 <= 0xBF) -> do
+                        rep (i + 3) j v
+                    | (c1 >= 0x80 && c1 <= 0xBF) -> do
+                        rep (i + 2) j v
+                    | otherwise ->
+                        rep (i + 1) j v
+
+             | otherwise -> do
+                 rep (i + 1) j v
+
+    rep i j v = do
+      U.write v (j + 0) 0xEF
+      U.write v (j + 1) 0xBF
+      U.write v (j + 2) 0xBD
+      go i (j + 3) v
+
+-- | Converts a storable vector to a byte string.
+dataString :: MVector s Word8 -> Int -> ST s ByteString
+dataString v n =
+  pure $ unsafePerformIO $ do
+    let (f, _) = U.unsafeToForeignPtr0 v
+    withForeignPtr f $ \p ->
+      S.packCStringLen (castPtr p, n)
diff --git a/test/TestSuite.hs b/test/TestSuite.hs
new file mode 100644
--- /dev/null
+++ b/test/TestSuite.hs
@@ -0,0 +1,11 @@
+module Main where
+
+import Zenacy.Unicode.Tests
+import Test.Framework
+  ( defaultMain
+  )
+
+main :: IO ()
+main = defaultMain
+  [ testUnicode
+  ]
diff --git a/test/Zenacy/Unicode/Tests.hs b/test/Zenacy/Unicode/Tests.hs
new file mode 100644
--- /dev/null
+++ b/test/Zenacy/Unicode/Tests.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Zenacy.Unicode.Tests
+  ( testUnicode
+  ) where
+
+import Zenacy.Unicode
+import Data.Monoid
+  ( (<>)
+  )
+import Test.Framework
+  ( Test
+  , testGroup
+  )
+import Test.Framework.Providers.HUnit
+  ( testCase
+  )
+import Test.HUnit
+  ( assertBool
+  , assertEqual
+  , assertFailure
+  )
+
+testUnicode :: Test
+testUnicode = testGroup "Zenacy.Unicode"
+  [ testBom
+  , testClean
+  ]
+
+testBom :: Test
+testBom = testCase "unicode bom" $ do
+  assertEqual "TEST 1" (Nothing, "a") $ bomStrip "a"
+  assertEqual "TEST 2" (Just BOM_UTF8, "abc") $ bomStrip (utf8 <> "abc")
+  assertEqual "TEST 3" (Just BOM_UTF16_BE, "abc") $ bomStrip (utf16be <> "abc")
+  assertEqual "TEST 4" (Just BOM_UTF16_LE, "abc") $ bomStrip (utf16le <> "abc")
+  assertEqual "TEST 5" (Just BOM_UTF32_BE, "abc") $ bomStrip (utf32be <> "abc")
+  assertEqual "TEST 6" (Just BOM_UTF32_LE, "abc") $ bomStrip (utf32le <> "abc")
+  where
+    utf8 = "\xEF\xBB\xBF"
+    utf16be = "\xFE\xFF"
+    utf16le = "\xFF\xFE"
+    utf32be = "\x00\x00\xFE\xFF"
+    utf32le = "\xFF\xFE\x00\x00"
+
+testClean :: Test
+testClean = testCase "unicode clean" $ do
+  assertEqual "TEST 1" "a" $ unicodeCleanUTF8 "a"
+  assertEqual "TEST 2" "abc" $ unicodeCleanUTF8 "abc"
+  assertEqual "TEST 3" "\xEF\xBF\xBD" $ unicodeCleanUTF8 "\x00"
+  assertEqual "TEST 4" "\xEF\xBF\xBD" $ unicodeCleanUTF8 "\x92"
+  assertEqual "TEST 5"
+    "government\xEF\xBF\xBDs arguments" $
+    unicodeCleanUTF8 "government\x92s arguments"
diff --git a/zenacy-unicode.cabal b/zenacy-unicode.cabal
new file mode 100644
--- /dev/null
+++ b/zenacy-unicode.cabal
@@ -0,0 +1,79 @@
+cabal-version: >= 1.10
+version: 1.0.0
+name:
+  zenacy-unicode
+synopsis:
+  Unicode utilities for Haskell
+description:
+  Zenacy Unicode includes tools for checking byte order marks (BOM) and
+  cleaning data to remove invalid bytes.  These tools can help ensure that
+  data pulled from the web can be parsed and converted to text.
+homepage:
+  https://github.com/mlcfp/zenacy-unicode
+license:
+  MIT
+license-file:
+  LICENSE
+author:
+  Michael Williams <mlcfp@icloud.com>
+maintainer:
+  Michael Williams <mlcfp@icloud.com>
+copyright:
+  Copyright (C) 2015-2020 Michael P Williams
+category:
+  Web
+build-type:
+  Simple
+extra-source-files:
+  README.md CHANGES.md
+
+source-repository head
+  type:     git
+  location: https://github.com/mlcfp/zenacy-unicode.git
+
+library
+  hs-source-dirs:
+    src
+  exposed-modules:
+    Zenacy.Unicode
+  build-depends:
+    base              == 4.*,
+    bytestring        >= 0.10.6.0 && < 0.11,
+    vector            >= 0.11 && < 0.13,
+    word8             >= 0.1.2 && < 0.2
+
+  ghc-options:
+    -O3 -Wall
+    -Wno-name-shadowing
+    -Wno-unused-matches
+    -Wno-unused-local-binds
+    -Wno-unused-imports
+    -Wno-unused-top-binds
+    -Wno-incomplete-patterns
+  default-extensions:
+  default-language:
+    Haskell2010
+
+test-suite zenacy-unicode-test
+  type:
+    exitcode-stdio-1.0
+  hs-source-dirs:
+    test
+  main-is:
+    TestSuite.hs
+  build-depends:
+    base == 4.*
+    , bytestring
+    , HUnit
+    , test-framework
+    , test-framework-hunit
+    , text
+    , zenacy-unicode
+  default-extensions:
+  ghc-options:
+    -O3 -threaded -rtsopts -with-rtsopts=-N
+  default-language:
+    Haskell2010
+  other-modules:
+    Zenacy.Unicode.Tests
+
