Compare commits

..

No commits in common. "master" and "0.3.1" have entirely different histories.

8 changed files with 128 additions and 409 deletions

View File

3
.gitignore vendored
View File

@ -1,4 +1 @@
dist dist
dist-newstyle
result
bisc.nix

302
Main.hs
View File

@ -4,100 +4,41 @@
{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiWayIf #-}
-- Databases import Data.List (nub, foldl')
import Database.Selda (Text, liftIO, (!)) import Data.Maybe (mapMaybe)
import Database.Selda.SQLite (withSQLite) import Data.Function ((&))
import Control.Monad.Trans.Resource (ResourceT) import Data.Default (def)
import Data.Text.Encoding (decodeUtf8)
import Control.Monad (mapM_, when, (>=>))
import Control.Monad.Reader (ReaderT, runReaderT, asks)
import Control.Monad.Except (ExceptT, runExceptT, throwError)
import System.FilePath (joinPath, takeBaseName, (</>))
import Database.Selda (Text, liftIO, (.||), (!))
import Database.Selda.SQLite (withSQLite)
import qualified Database.Selda as S import qualified Database.Selda as S
import qualified Database.LevelDB as L import qualified Database.LevelDB as L
import qualified Database.LevelDB.Streaming as LS import qualified Database.LevelDB.Streaming as LS
-- Error handling import Control.Exception as BE
import Control.Exception as BE import Control.Monad.Catch as CE
import Control.Monad.Catch as CE
import qualified System.Exit as E
-- Configuration import qualified System.Directory as D
import qualified Options.Applicative as O import qualified Data.Configurator as C
import qualified System.Directory as D
import qualified Data.Configurator as C
-- Text converion
import Data.Text.Encoding (decodeUtf8)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.IO as T import qualified Data.Text.IO as T
import qualified Data.ByteString as B import qualified Data.ByteString as B
-- Version information import Debug.Trace
import qualified Paths_bisc as Bisc
import Data.Version (showVersion)
-- File locking bypass -- | Bisc settings
import qualified System.Posix.Files as Posix
-- Misc
import Data.List (nub, isInfixOf)
import Data.Maybe (mapMaybe)
import Data.Function ((&))
import Data.Default (def)
import Control.Monad (when)
import Control.Monad.Reader (ReaderT, runReaderT, asks)
import Control.Monad.Except (ExceptT, runExceptT, throwError)
import System.FilePath (joinPath, takeBaseName, (</>))
-- Options
-- | Configuration file settings
data Settings = Settings data Settings = Settings
{ webenginePath :: FilePath -- ^ webengine data directory { whitelistPath :: FilePath -- ^ whitelist file
, webenginePath :: FilePath -- ^ webengine data directory
, whitelist :: [Text] -- ^ whitelisted domains , whitelist :: [Text] -- ^ whitelisted domains
, options :: Options -- ^ cli options
} }
-- | Command line options
data Options = Options
{ version :: Bool -- ^ print version number
, dryRun :: Bool -- ^ don't delete anything
, unsafe :: Bool -- ^ ignore locks
, configPath :: FilePath -- ^ config file path
}
-- | Command line parser
cliParser :: FilePath -> O.ParserInfo Options
cliParser defConfig = O.info (O.helper <*> parser) infos
where
parser = Options
<$> O.switch
( O.long "version"
<> O.short 'v'
<> O.help "Print the version number and exit"
)
<*> O.switch
( O.long "dry-run"
<> O.short 'n'
<> O.help ("Don't actually remove anything, "<>
"just show what would be done")
)
<*> O.switch
( O.long "unsafe"
<> O.short 'u'
<> O.help ("Ignore database locks. " <>
"This will probably corrupt the databases, but " <>
"works while the browser is running.")
)
<*> O.strOption
( O.long "config"
<> O.short 'c'
<> O.value defConfig
<> O.help "Specify a configuration file"
)
infos =
O.fullDesc <>
O.progDesc "A small tool that clears cookies (and more)"
-- SQL records -- SQL records
@ -127,7 +68,6 @@ cookies = S.table "cookies" []
quotaOrigins :: S.Table QuotaOrigin quotaOrigins :: S.Table QuotaOrigin
quotaOrigins = S.table "OriginInfoTable" [] quotaOrigins = S.table "OriginInfoTable" []
-- | Main monad stack -- | Main monad stack
-- --
-- * 'ReaderT' for accessing settings -- * 'ReaderT' for accessing settings
@ -143,79 +83,49 @@ type Result = (Int, [Text])
-- | Clears all means of permanent storage -- | Clears all means of permanent storage
main :: IO () main :: IO ()
main = do main = do
defConfig <- D.getXdgDirectory D.XdgConfig ("bisc" </> "bisc.conf") config <- D.getXdgDirectory D.XdgConfig ("bisc" </> "bisc.conf")
opts <- O.execParser (cliParser defConfig) run <- runAction <$> loadSettings config
run "Cookies" deleteCookies
when (version opts) $ do run "QuotaManager" deleteQuotaOrigins
putStrLn ("bisc " <> showVersion Bisc.version) run "IndexedDB" deleteIndexedDB
E.exitSuccess run "LocalStorage" deleteLocalStorage
run "SessionStorage" deleteSessionStorage
run <- runAction <$> loadSettings opts
numFailures <- sum <$> mapM (uncurry run) actions
if numFailures == 0
then E.exitSuccess
else do
putStrLn ("\nwarning: " <> show numFailures <> " actions have failed")
E.exitWith (E.ExitFailure numFailures)
-- | Runs an 'Action' and pretty-prints the results -- | Runs an 'Action' and pretty-prints the results
runAction :: Settings -> Text -> Action Result -> IO Int runAction :: Settings -> Text -> Action Result -> IO ()
runAction settings name x = do runAction settings name x = do
a <- BE.try $ runExceptT (runReaderT x settings) a <- BE.try $ runExceptT (runReaderT x settings)
case a of case a of
Right (Right res) -> printResult res >> return 0 Right (Right res) -> printResult res
Right (Left msg) -> printFailed msg >> return 1 Right (Left msg) -> printFailed msg
Left (err :: BE.IOException) -> Left (err :: BE.IOException) -> printFailed (T.pack $ BE.displayException err)
printFailed (T.pack $ BE.displayException err) >> return 1
where where
printFailed msg = printFailed msg = T.putStrLn ("- " <> name <> " cleaning failed:\n " <> msg)
T.putStrLn ("- " <> name <> " cleaning failed:\n " <> msg)
printResult (n, bad) printResult (n, bad)
| n > 0 = do | n > 0 = do
T.putStrLn ("- " <> name <> ": " <> verb <> T.putStrLn ("- " <> name <> ": deleted " <> T.pack (show n) <> " entries for:")
" " <> T.pack (show n) <> " entries for:")
T.putStrLn (T.unlines $ map (" * " <>) bad) T.putStrLn (T.unlines $ map (" * " <>) bad)
| otherwise = T.putStrLn ("- " <> name <> ": nothing to delete") | otherwise = T.putStrLn ("- " <> name <> ": nothing to delete")
verb = if (dryRun . options $ settings)
then "would delete"
else "deleted"
-- * Cleaning actions -- * Cleaning actions
-- | List of actions and their names
actions :: [(Text, Action Result)]
actions =
[ ("Cookies", deleteCookies)
, ("QuotaManager", deleteQuotaOrigins)
, ("IndexedDB", deleteIndexedDB)
, ("LocalStorage", deleteLocalStorage)
, ("SessionStorage", deleteSessionStorage)
]
-- | Deletes records in the Cookies database -- | Deletes records in the Cookies database
deleteCookies :: Action Result deleteCookies :: Action Result
deleteCookies = do deleteCookies = do
dir <- asks webenginePath database <- (</> "Cookies") <$> asks webenginePath
dry <- asks (dryRun . options) exists <- liftIO $ D.doesFileExist database
-- check for database
exists <- liftIO $ D.doesFileExist (dir </> "Cookies")
when (not exists) (throwError "database is missing") when (not exists) (throwError "database is missing")
whitelist <- map S.text <$> asks whitelist whitelist <- map S.text <$> asks whitelist
withoutLocks "Cookies" $ \database -> do CE.handle dbErrors $ withSQLite database $ do
CE.handle dbErrors $ withSQLite database $ do bad <- S.query $ do
bad <- S.query $ do cookie <- S.select cookies
cookie <- S.select cookies S.restrict (by whitelist cookie)
S.restrict (by whitelist cookie) return (cookie ! #host_key)
return (cookie ! #host_key) n <- S.deleteFrom cookies (by whitelist)
when (not dry) $ return (n, nub bad)
S.deleteFrom_ cookies (by whitelist)
return (length bad, nub bad)
where where
by set x = S.not_ (x ! #host_key `S.isIn` set) by set x = S.not_ (x ! #host_key `S.isIn` set)
@ -223,23 +133,18 @@ deleteCookies = do
-- | Deletes records in the QuotaManager API database -- | Deletes records in the QuotaManager API database
deleteQuotaOrigins :: Action Result deleteQuotaOrigins :: Action Result
deleteQuotaOrigins = do deleteQuotaOrigins = do
dir <- asks webenginePath database <- (</> "QuotaManager") <$> asks webenginePath
dry <- asks (dryRun . options) exists <- liftIO $ D.doesFileExist database
-- check for database
exists <- liftIO $ D.doesFileExist (dir </> "QuotaManager")
when (not exists) (throwError "database is missing") when (not exists) (throwError "database is missing")
whitelist <- map mkPattern <$> asks whitelist whitelist <- map pattern <$> asks whitelist
withoutLocks "QuotaManager" $ \database -> do CE.handle dbErrors $ withSQLite database $ do
CE.handle dbErrors $ withSQLite database $ do bad <- S.query $ do
bad <- S.query $ do quota <- S.select quotaOrigins
quota <- S.select quotaOrigins S.restrict (by whitelist quota)
S.restrict (by whitelist quota) return (quota ! #origin)
return (quota ! #origin) n <- S.deleteFrom quotaOrigins (by whitelist)
when (not dry) $ return (n, nub bad)
S.deleteFrom_ quotaOrigins (by whitelist)
return (length bad, nub bad)
where where
-- check if quota is not whitelisted -- check if quota is not whitelisted
by whitelist quota = S.not_ (S.true `S.isIn` matches) by whitelist quota = S.not_ (S.true `S.isIn` matches)
@ -250,7 +155,8 @@ deleteQuotaOrigins = do
S.restrict (url `S.like` S.the pattern) S.restrict (url `S.like` S.the pattern)
return S.true return S.true
-- turns domains into patterns to match a url -- turns domains into patterns to match a url
mkPattern domain = "http%://%" <> domain <> "/" pattern domain = "http%://%" <> domain <> "/"
-- | Deletes per-domain files under the IndexedDB directory -- | Deletes per-domain files under the IndexedDB directory
@ -263,17 +169,15 @@ deleteQuotaOrigins = do
deleteIndexedDB :: Action Result deleteIndexedDB :: Action Result
deleteIndexedDB = do deleteIndexedDB = do
webengine <- asks webenginePath webengine <- asks webenginePath
dry <- asks (dryRun . options)
exists <- liftIO $ D.doesDirectoryExist (webengine </> "IndexedDB") exists <- liftIO $ D.doesDirectoryExist (webengine </> "IndexedDB")
when (not exists) $ throwError "directory is missing" when (not exists) $ throwError "directory is missing"
entries <- listDirectoryAbs (webengine </> "IndexedDB") entries <- listDirectoryAbs (webengine </> "IndexedDB")
unlisted <- (\domains -> not . (`elem` domains)) <$> asks whitelist unlisted <- (\domains -> not . (`elem` domains)) <$> asks whitelist
let let
badFiles = filterMaybe (fmap unlisted . domain) entries badFiles = filterMaybe (fmap unlisted . domain) entries
badDomains = mapMaybe domain badFiles badDomains = mapMaybe domain badFiles
when (not dry) $ liftIO $ mapM_ D.removePathForcibly badFiles
liftIO $ mapM_ D.removePathForcibly badFiles
return (length badFiles, nub badDomains) return (length badFiles, nub badDomains)
where where
listDirectoryAbs :: FilePath -> Action [FilePath] listDirectoryAbs :: FilePath -> Action [FilePath]
@ -289,8 +193,8 @@ deleteIndexedDB = do
domain :: FilePath -> Maybe Text domain :: FilePath -> Maybe Text
domain = extract . url where domain = extract . url where
extract [] = Nothing extract [] = Nothing
extract (_:[]) = Nothing extract (x:[]) = Nothing
extract (_:xs) = Just $ T.unwords (init xs) extract (x:xs) = Just $ T.unwords (init xs)
url = T.splitOn "_" . T.pack . takeBaseName url = T.splitOn "_" . T.pack . takeBaseName
@ -309,29 +213,18 @@ deleteLocalStorage = do
whitelist <- asks whitelist whitelist <- asks whitelist
let path = webengine </> "Local Storage" </> "leveldb" let path = webengine </> "Local Storage" </> "leveldb"
dry <- asks (dryRun . options)
unsafe <- asks (unsafe . options)
when (not dry && unsafe) $ liftIO $ do
-- delete and recreate the lock file to bypass POSIX locks
D.removeFile (path </> "LOCK")
T.writeFile (path </> "LOCK") ""
dbIsOk <- liftIO $ D.doesFileExist (path </> "LOCK") dbIsOk <- liftIO $ D.doesFileExist (path </> "LOCK")
when (not dbIsOk) (throwError "database is missing or corrupted") when (not dbIsOk) (throwError "database is missing or corrupted")
version <- withRetryDB path (\db -> L.get db def "VERSION") version <- withRetryDB path (\db -> L.get db def "VERSION")
when (version /= Just "1") (throwError "database is empty or the schema unsupported") when (version /= Just "1") (throwError "database is empty or the schema unsupported")
-- when dry running replace the delete function with a nop
let delete = if dry then (\_ _ _ -> pure ()) else L.delete
withDB path $ \db -> do withDB path $ \db -> do
badDomains <- L.withIterator db def $ \i -> badDomains <- L.withIterator db def $ \i ->
LS.keySlice i LS.AllKeys LS.Asc LS.keySlice i LS.AllKeys LS.Asc
& LS.filter (\k -> "META:" `B.isPrefixOf ` k & LS.filter (\k -> "META:" `B.isPrefixOf ` k
&& (metaDomain k) `notElem` whitelist) && (metaDomain k) `notElem` whitelist)
& LS.mapM (\k -> delete db def k >> return (metaDomain k)) & LS.mapM (\k -> L.delete db def k >> return (metaDomain k))
& LS.toList & LS.toList
n <- L.withIterator db def $ \i -> n <- L.withIterator db def $ \i ->
@ -339,7 +232,7 @@ deleteLocalStorage = do
& LS.filter (\k -> "_" `B.isPrefixOf` k & LS.filter (\k -> "_" `B.isPrefixOf` k
&& "\NUL\SOH" `B.isInfixOf` k && "\NUL\SOH" `B.isInfixOf` k
&& (recDomain k) `notElem` whitelist) && (recDomain k) `notElem` whitelist)
& LS.mapM (delete db def) & LS.mapM (L.delete db def)
& LS.length & LS.length
return (n, badDomains) return (n, badDomains)
@ -365,23 +258,12 @@ deleteSessionStorage = do
whitelist <- asks whitelist whitelist <- asks whitelist
let path = webengine </> "Session Storage" let path = webengine </> "Session Storage"
dry <- asks (dryRun . options)
unsafe <- asks (unsafe . options)
when (not dry && unsafe) $ liftIO $ do
-- delete and recreate the lock file to bypass POSIX locks
D.removeFile (path </> "LOCK")
T.writeFile (path </> "LOCK") ""
dbIsOk <- liftIO $ D.doesFileExist (path </> "LOCK") dbIsOk <- liftIO $ D.doesFileExist (path </> "LOCK")
when (not dbIsOk) (throwError "database is missing or corrupted") when (not dbIsOk) (throwError "database is missing or corrupted")
version <- withRetryDB path (\db -> L.get db def "version") version <- withRetryDB path (\db -> L.get db def "version")
when (version /= Just "1") (throwError "database is empty or the schema unsupported") when (version /= Just "1") (throwError "database is empty or the schema unsupported")
-- when dry running replace the delete function with a nop
let delete = if dry then (\_ _ _ -> pure ()) else L.delete
withDB path $ \db -> do withDB path $ \db -> do
-- map of id -> isBad -- map of id -> isBad
badMap <- L.withIterator db def $ \i -> badMap <- L.withIterator db def $ \i ->
@ -395,7 +277,7 @@ deleteSessionStorage = do
LS.keySlice i LS.AllKeys LS.Asc LS.keySlice i LS.AllKeys LS.Asc
& LS.filter (B.isPrefixOf "namespace") & LS.filter (B.isPrefixOf "namespace")
& LS.filter (isBad whitelist) & LS.filter (isBad whitelist)
& LS.mapM (\k -> delete db def k >> return (domain k)) & LS.mapM (\k -> L.delete db def k >> return (domain k))
& LS.toList & LS.toList
-- and their records -- and their records
@ -404,7 +286,7 @@ deleteSessionStorage = do
& LS.filter (B.isPrefixOf "map-") & LS.filter (B.isPrefixOf "map-")
& LS.mapM (\k -> & LS.mapM (\k ->
case lookup (originId k) badMap of case lookup (originId k) badMap of
Just True -> delete db def k >> return 1 Just True -> L.delete db def k >> return 1
_ -> return 0) _ -> return 0)
& LS.sum & LS.sum
return (n, nub badDomains) return (n, nub badDomains)
@ -420,72 +302,40 @@ deleteSessionStorage = do
-- * Helper functions -- * Helper functions
-- | Loads a leveldb database and runs a resourceT action -- | Loads a leveldb database and runs a resourceT action
withDB :: FilePath -> (L.DB -> ResourceT IO a) -> Action a --
-- withDB :: FilePath -> (L.DB -> ResourceT IO a) -> Action a
withDB path f = liftIO $ L.runResourceT (L.open path def >>= f) withDB path f = liftIO $ L.runResourceT (L.open path def >>= f)
-- | Like 'withDB' but retry the action after repairing the db -- | Like 'withDB' but retry the action after repairing the db
withRetryDB :: FilePath -> (L.DB -> ResourceT IO a) -> Action a --
-- withRetryDB :: FilePath -> (L.DB -> ResourceT IO a) -> Action a
withRetryDB path action = do withRetryDB path action = do
res <- CE.try (withDB path action) res <- CE.try (withDB path action)
case res of case res of
Right b -> return b Right b -> return b
Left (e :: BE.IOException) -> Left (e :: BE.IOException) ->
if | "Corruption" `T.isInfixOf` msg -> do if not ("Corruption" `T.isInfixOf` msg)
-- try repairing before giving up then throwError ("error opening the database:\n " <> msg)
liftIO $ L.repair path def else liftIO $ L.repair path def >> withDB path action
withDB path action
| "unavailable" `T.isInfixOf` msg ->
throwError "database is locked (in use by another process)"
| otherwise ->
throwError ("error opening the database:\n " <> msg)
where msg = T.pack (BE.displayException e) where msg = T.pack (BE.displayException e)
-- | Loads the config from a file
-- | Bypass SQLite locking mechanism loadSettings :: FilePath -> IO Settings
-- loadSettings path = do
-- SQLite manages concurrent access via POSIX locks: these are tied to a
-- specific file and pid. They can be bypassed by simply creating a hard
-- link (pointing to the same inode), editing the link and then removing it.
withoutLocks :: String -> (FilePath -> Action a) -> Action a
withoutLocks dbName cont = do
dir <- asks webenginePath
unsafe <- asks (unsafe . options)
let
real = dir </> dbName
link = real <> "-bypass"
-- bypass the SQLite POSIX locks with hard links
when unsafe $ liftIO (Posix.createLink real link)
res <- cont (if unsafe then link else real)
-- remove the hard links
when unsafe $ liftIO (Posix.removeLink link)
return res
-- | Loads the config file/cli options
loadSettings :: Options -> IO Settings
loadSettings opts = do
configdir <- D.getXdgDirectory D.XdgConfig "qutebrowser" configdir <- D.getXdgDirectory D.XdgConfig "qutebrowser"
datadir <- D.getXdgDirectory D.XdgData "qutebrowser" datadir <- D.getXdgDirectory D.XdgData "qutebrowser"
let let
defaultWhitelist = joinPath [configdir, "whitelists", "cookies"] defaultWhitelist = joinPath [configdir, "whitelists", "cookies"]
defaultWebengine = joinPath [datadir, "webengine"] defaultWebengine = joinPath [datadir, "webengine"]
config <- C.load [C.Optional (configPath opts)] config <- C.load [C.Optional path]
whitelist <- C.lookupDefault defaultWhitelist config "whitelist-path" whitelist <- C.lookupDefault defaultWhitelist config "whitelist-path"
webengine <- C.lookupDefault defaultWebengine config "webengine-path" webengine <- C.lookupDefault defaultWebengine config "webengine-path"
domains <- T.lines <$> T.readFile whitelist domains <- T.lines <$> T.readFile whitelist
return (Settings webengine domains opts) return (Settings whitelist webengine domains)
-- | Catches any Selda error -- | Catches any Selda error
dbErrors :: S.SeldaError -> Action a dbErrors :: S.SeldaError -> Action a
dbErrors (S.DbError msg) = throwError $ "error opening database: " <> T.pack msg dbErrors e = throwError $
dbErrors e = "database operation failed: " <> T.pack (BE.displayException e)
if "ErrorBusy" `isInfixOf` msg
then throwError "database is locked (in use by another process)"
else throwError $ "database operation failed: " <> T.pack msg
where msg = BE.displayException e

View File

@ -2,28 +2,29 @@
### A small tool that clears cookies (and more) ### A small tool that clears cookies (and more)
Websites can store unwanted data using all sorts of methods: besides the usual Websites can store unwanted data using all sorts of methods: besides
cookies, there are also the local and session storage, the IndexedDB API and the usual cookies, there are also the local and session storage, the
more caches as well. IndexedDB API and more caches as well.
bisc will try to go through each of them and remove all information from bisc will try to go through each of them and remove all information from
websites that are not explicitly allowed (ie. a whitelist of domains). websites that are not explicitly allowed (ie. a whitelist of domains).
It was created for qutebrowser, but it actually supports the storage format It was created for qutebrowser, but it actually supports the storage
used by Chromium-based browsers, which (sadly) means almost every one nowadays. format used by Chromium-based browsers, which (sadly) means almost
every one nowadays.
## Installation ## Installation
bisc is a Haskell program available on [Hackage][hackage] and can be installed bisc is a Haskell program available on [Hackage][hackage] and can
with one of the Haskell package managers. For example, with be installed with one of the Haskell package managers. For
[cabal-install][cabal] you would do example, with [cabal-install][cabal] you would do
``` ```
cabal install bisc cabal install bisc
``` ```
and similarly for [stack][stack]. and similarly for [stack][stack].
Alternatively, if you are using Nix or NixOS, bisc is available under the Alternatively, if you are using Nix or NixOS, bisc is available
attribute `haskellPackages.bisc`. It should also be in the Nix binary cache so under the attribute `haskellPackages.bisc`. It should also be in
you don't have to build from source. the Nix binary cache so you don't have to build from source.
Finally, statically compiled binaries can be found in the Finally, statically compiled binaries can be found in the
[releases](/git/rnhmjoj/bisc/releases). [releases](/git/rnhmjoj/bisc/releases).
@ -34,29 +35,26 @@ Finally, statically compiled binaries can be found in the
## Configuration ## Configuration
The bisc configuration file is `$XDG_CONFIG_HOME/bisc/bisc.conf`. It allows to The bisc configuration file is `$XDG_CONFIG_HOME/bisc/bisc.conf`.
change the paths of the QtWebEngine/Chromium directory and the whitelist file. It allows to change the paths of the QtWebEngine/Chromium
directory and the whitelist file.
The default settings are: The default settings are:
``` ```
whitelist-path = "$(XDG_CONFIG_HOME)/qutebrowser/whitelists/cookies" whitelist-path = "$(XDG_CONFIG_HOME)/qutebrowser/whitelists/cookies"
webengine-path = "$(XDG_DATA_HOME)/qutebrowser/webengine" webengine-path = "$(XDG_DATA_HOME)/qutebrowser/webengine"
``` ```
If you want a different location for the configuration file, you can change it
using the `--config` command line option.
## Usage ## Usage
- Create an empty whitelist file and write the domains of the allowed cookies, Create an empty whitelist file and write the domains of the
one per line. allowed cookies, one per line.
Eg. Eg.
``` ```
.example.com .example.com
example.com example.com
``` ```
- Run `bisc --dry-run` to see what would be deleted without actually doing it. Run `bisc` to delete all non-whitelisted data from qutebrowser.
- Run `bisc` to delete all non-whitelisted data from qutebrowser.
Note that running bisc while the browser is open is not safe: this means it Note that running bisc while the browser is open is not safe: this means it
could possibly **corrupt** the databases. Hoever, corruption in the sqllite could possibly **corrupt** the databases. Hoever, corruption in the sqllite
@ -66,7 +64,7 @@ corrupt more often, are automatically repaired by bisc.
## License ## License
Copyright (C) 2022 Michele Guerini Rocco Copyright (C) 2021 Michele Guerini Rocco
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View File

@ -1,5 +1,5 @@
name: bisc name: bisc
version: 0.4.1.0 version: 0.3.1.0
synopsis: A small tool that clears cookies (and more). synopsis: A small tool that clears cookies (and more).
description: description:
@ -19,30 +19,23 @@ license: GPL-3
license-file: LICENSE license-file: LICENSE
author: Michele Guerini Rocco author: Michele Guerini Rocco
maintainer: rnhmjoj@inventati.org maintainer: rnhmjoj@inventati.org
copyright: Copyright (C) 2022 Michele Guerini Rocco copyright: Copyright (C) 2021 Michele Guerini Rocco
category: Utility category: Utility
build-type: Simple build-type: Simple
extra-source-files: README.md, man/bisc.1 man/bisc.conf.5 extra-source-files: README.md
cabal-version: >=1.10 cabal-version: >=1.10
source-repository head source-repository head
type: git type: git
location: https://maxwell.ydns.eu/git/rnhmjoj/bisc location: https://maxwell.ydns.eu/git/rnhmjoj/bisc
flag static
default: False
description: Create a statically-linked binary
executable bisc executable bisc
main-is: Main.hs main-is: Main.hs
build-depends: base ==4.* , selda ==0.*, build-depends: base ==4.* , selda ==0.*,
selda-sqlite ==0.*, selda-sqlite ==0.*,
leveldb-haskell ==0.*, resourcet, leveldb-haskell ==0.*,
filepath, directory, text, unix, filepath, directory, text,
mtl, configurator, exceptions, mtl, configurator, exceptions,
data-default, bytestring, data-default, bytestring
optparse-applicative
default-language: Haskell2010 default-language: Haskell2010
ghc-options: -Wall -Wno-name-shadowing -O2 extra-libraries: snappy stdc++
if flag(static)
extra-libraries: snappy stdc++

View File

@ -10,32 +10,37 @@ let
basepkgs = import nixpkgs { inherit system; }; basepkgs = import nixpkgs { inherit system; };
pkgs = if static then basepkgs.pkgsStatic else basepkgs.pkgs; pkgs = if static then basepkgs.pkgsStatic else basepkgs.pkgs;
ghc = if static then pkgs.haskell.packages.integer-simple.ghc901 f = { mkDerivation, base, bytestring, configurator, data-default
, directory, exceptions, filepath, leveldb-haskell, mtl, selda
, selda-sqlite , lib, text
}:
mkDerivation {
pname = "bisc";
version = "0.3.0.0";
src = ./.;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [
base bytestring configurator data-default directory exceptions
filepath leveldb-haskell mtl selda selda-sqlite text
];
executableSystemDepends = [ pkgs.snappy ];
buildFlags = lib.optionals static [
"--ld-option=-lstdc++"
"--ld-option=-lsnappy"
];
homepage = "https://maxwell.ydns.eu/git/rnhmjoj/bisc";
description = "A small tool that clears cookies (and more)";
license = lib.licenses.gpl3;
};
ghc = if static then pkgs.haskell.packages.integer-simple.ghc8104
else if compiler == "default" then pkgs.haskellPackages else if compiler == "default" then pkgs.haskellPackages
else pkgs.haskell.packages.${compiler}; else pkgs.haskell.packages.${compiler};
variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id; variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id;
drv = variant (override (ghc.callPackage ./bisc.nix {})); drv = variant (ghc.callPackage f {});
override = drv: pkgs.haskell.lib.overrideCabal drv (old: with pkgs.lib; {
buildTools = [ pkgs.installShellFiles ];
configureFlags = optional static "-f static";
buildFlags = optionals static [
"--ld-option=-lstdc++"
"--ld-option=-lsnappy"
];
postInstall = ''
# generate completion
$out/bin/bisc --bash-completion-script "$out/bin/bisc" > bisc.bash
$out/bin/bisc --fish-completion-script "$out/bin/bisc" > bisc.fish
$out/bin/bisc --zsh-completion-script "$out/bin/bisc" > bisc.zsh
installShellCompletion bisc.{bash,fish,zsh}
installManPage man/*.[0-9]
'';
postFixup = optionalString static "rm -r $out/nix-support";
});
in in

View File

@ -1,75 +0,0 @@
.TH bisc 1 "January 11, 2022" "bisc 0.4.1" "User Commands"
.SH NAME
bisc - a small tool that clears cookies (and more)
.SH SYNOPSIS
.B bisc
.RI [ option ]
.SH DESCRIPTION
.PP
Websites can store unwanted data using all sorts of methods: besides the usual
cookies, there are also the local and session storage, the IndexedDB API and
more caches as well.
.PP
Bisc will try to go through each of them and remove all information from
websites that are not explicitly allowed (ie. a whitelist of domains).
It was created for qutebrowser, but it actually supports the storage format
used by Chromium-based browsers, which (sadly) means almost every one nowadays.
.SH USAGE
.IP \(bu 2
Create an empty whitelist file (see the FILES section) and write the domains of
the allowed cookies, one per line. For example:
.IP
.nf
\fC
\&.example.com
example.com
\fR
.fi
.IP \(bu 2
Run \fCbisc --dry-run\fR to see what would be deleted without actually
doing it.
.IP \(bu 2
Run \fCbisc\fR to delete all non-whitelisted data from qutebrowser.
.SH OPTIONS
.TP
.BR -c ","\ --config\ FILE
Use FILE as the configuration file.
.TP
.BR -n ","\ --dry-run
Don't actually remove anything, just show what would be done.
.TP
.BR -u ","\ --unsafe
Ignore database locks.
This will probably corrupt the databases, but works while the browser is
running.
.TP
.BR -h ","\ --help
Show the program information and help screen.
.SH FILES
.TP
.I $XDG_CONFIG_HOME/bisc/bisc.conf
Bisc configuration
.TP
.I $XDG_CONFIG_HOME/qutebrowser/whitelists/cookies
Domain whitelist
.TP
.I $XDG_DATA_HOME/qutebrowser/webengine
Chromium/QtWebEngine state directory
.PP
Note: when the variable $XDG_CONFIG_HOME or $XDG_DATA_HOME is not set,
$HOME/.config and $HOME/.local/share respectively, will be used instead.
.SH SEE ALSO
\fBbisc.conf\fR(5) for the bisc configuration file
.SH AUTHORS
Copyright © 2022 Michele Guerini Rocco.
.TP 0
Released under the GPL, version 3 or greater.
This software carries no warranty of any kind.

View File

@ -1,49 +0,0 @@
.TH bisc.conf 5 "January 11, 2022" "bisc 0.4.1"
.SH NAME
bisc.conf - bisc configuration file
.SH SYNOPSIS
The bisc configuration file, found at the following locations, unless specified
via the \fC-c\fR command line option:
.IP \(bu 3
$XDG_CONFIG_HOME/bisc/bisc.conf,
.IP \(bu 3
$HOME/.config/bisc/bisc.conf (when $XDG_CONFIG_HOME is not set)
.SH DESCRIPTION
.PP
The bisc.conf file allows to change the default location of a couple of files
used by bisc.
.SH OPTIONS
.TP 4
.BR "webengine-path" " (default " "$(XDG_DATA_HOME)/qutebrowser/webengine")
The location of the Chromium/QtWebEngine state directory.
.TP 4
.BR "whitelist-path" " (default " "$(XDG_CONFIG_HOME)/qutebrowser/whitelists/cookies")
The location of the domain whitelist.
.SH EXAMPLE
This is an example configuration:
.IP
.nf
\fC
# This is a comment
whitelist-path = "/home/alice/docs/cookie-whitelist"
# You can also access environment variables:
webengine-path = "$(HOME)/.local/qutebrowser/webengine"
\fR
.fi
.SH SEE ALSO
\fBbisc\fR(1) for the bisc command
.SH AUTHORS
Copyright © 2022 Michele Guerini Rocco.
.TP 0
Released under the GPL, version 3 or greater.
This software carries no warranty of any kind.