diff --git a/Control/Shell/Download.hs b/Control/Shell/Download.hs
new file mode 100644
--- /dev/null
+++ b/Control/Shell/Download.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- | High level functions for downloading files.
+module Control.Shell.Download
+  ( URI
+  , fetch, fetchBytes
+  , fetchFile
+  , fetchTags, fetchXML, fetchFeed
+  ) where
+import Data.ByteString as BS (ByteString, writeFile)
+import Data.String
+import Network.HTTP
+import qualified Network.URI as U
+import Text.Feed.Import (parseFeedString)
+import Text.Feed.Types (Feed)
+import Text.HTML.TagSoup (Tag, parseTags)
+import Text.XML.Light (Content, parseXML)
+import Control.Shell
+
+-- | A Uniform Resource Locator.
+type URI = String
+
+liftE :: Show e => IO (Either e a) -> Shell a
+liftE m = do
+  res <- liftIO m
+  case res of
+    Left e  -> fail (show e)
+    Right x -> return x
+
+httpFail :: (Int, Int, Int) -> String -> Shell a
+httpFail (a,b,c) reason =
+  fail $ "HTTP error " ++ concat [show a, show b, show c] ++ ": " ++ reason
+
+fetchSomething :: (IsString a, HStream a) => URI -> Shell a
+fetchSomething uri = do
+    u <- assert ("could not parse URI `" ++ uri ++ "'") (U.parseURI uri)
+    rsp <- liftE $ simpleHTTP (req u)
+    case rspCode rsp of
+      (2,_,_) -> return (rspBody rsp)
+      code    -> httpFail code (rspReason rsp)
+  where
+    req u = Request {
+        rqURI = u,
+        rqMethod = GET,
+        rqHeaders = [],
+        rqBody = ""
+      }
+
+-- | Download content specified by a url using curl, returning the content
+--   as a strict 'ByteString'.
+fetchBytes :: URI -> Shell ByteString
+fetchBytes = fetchSomething
+
+-- | Download content specified by a url using curl, returning the content
+--   as a 'String'.
+fetch :: URI -> Shell String
+fetch = fetchSomething
+
+-- | Download content specified by a url using curl, writing the content to
+--   the file specified by the given 'FilePath'.
+fetchFile :: FilePath -> URI -> Shell ()
+fetchFile file = fetchBytes >=> liftIO . BS.writeFile file
+
+-- | Download the content as for 'fetch', but return it as a list of parsed
+--   tags using the tagsoup html parser.
+fetchTags :: URI -> Shell [Tag String]
+fetchTags = fmap parseTags . fetch
+
+-- | Download the content as for 'fetch', but return it as parsed XML, using
+--   the xml-light parser.
+fetchXML :: URI -> Shell [Content]
+fetchXML = fmap parseXML . fetch
+
+-- | Download the content as for 'fetch', but return it as as parsed RSS or
+--   Atom content, using the feed library parser.
+fetchFeed :: URI -> Shell Feed
+fetchFeed uri = do
+  str <- fetch uri
+  assert ("could not parse feed from `" ++ uri ++ "'") (parseFeedString str)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2016, Anton Ekblad
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Anton Ekblad nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/shellmate-extras.cabal b/shellmate-extras.cabal
new file mode 100644
--- /dev/null
+++ b/shellmate-extras.cabal
@@ -0,0 +1,32 @@
+name:                shellmate-extras
+version:             0.3
+synopsis:            Extra functionality for shellmate.
+description:         HTTP downloads and parsing for various file formats.
+homepage:            https://github.com/valderman/shellmate
+license:             BSD3
+license-file:        LICENSE
+author:              Anton Ekblad
+maintainer:          anton@ekblad.cc
+category:            System
+build-type:          Simple
+cabal-version:       >=1.10
+bug-reports:         https://github.com/valderman/shellmate/issues
+
+source-repository head
+    type:       git
+    location:   https://github.com/valderman/shellmate.git
+
+library
+  exposed-modules:
+    Control.Shell.Download
+  build-depends:
+    base        >=4.7    && <5,
+    bytestring  >=0.10   && <0.11,
+    HTTP        >=4000.2 && <4000.4,
+    network-uri >=2.6    && <2.7,
+    feed        >=0.3    && <0.4,
+    tagsoup     >=0.13   && <0.14,
+    xml         >=1.3    && <1.4,
+    shellmate   >=0.3    && <0.4
+  default-language:
+    Haskell2010
