summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--hprox.cabal8
-rw-r--r--src/HProx.hs54
-rw-r--r--src/Main.hs25
4 files changed, 29 insertions, 63 deletions
diff --git a/README.md b/README.md
index 0cf0576..74434c4 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
* Basic HTTP proxy functionality.
* Simple password authentication.
-* TLS encryption (requires a valid certificate). Supports TLS 1.3 and HTTP 2, also known as SPDY Proxy.
+* TLS encryption (requires a valid certificate). Supports TLS 1.3 and HTTP/2, also known as SPDY Proxy.
* TLS SNI validation (blocks all clients with invalid domain name).
* Provide PAC file for easy client side configuration (supports Chrome and Firefox).
* Websocket redirection (compatible with [v2ray-plugin](https://github.com/shadowsocks/v2ray-plugin)).
@@ -32,6 +32,8 @@ stack setup
stack install
```
+Alternatively, you also can use the statically linked binary for the [latest release](https://github.com/bjin/hprox/releases).
+
### Usage
Use `hprox --help` to list options with detailed explanation.
@@ -62,7 +64,6 @@ Clients will be able to connect with plugin option `tls;host=example.com`.
### Known Issue
-* Only HTTP servers are supported as websocket and reverse proxy redirection destination.
* Passwords are currently stored in plain text, please set permission accordingly and
avoid using existing password.
diff --git a/hprox.cabal b/hprox.cabal
index f22e7c7..0aaf5e3 100644
--- a/hprox.cabal
+++ b/hprox.cabal
@@ -1,13 +1,13 @@
cabal-version: 1.12
--- This file has been generated from package.yaml by hpack version 0.31.1.
+-- This file has been generated from package.yaml by hpack version 0.31.2.
--
-- see: https://github.com/sol/hpack
--
--- hash: 7456eded738e088a6f5195326db8a89c8149a54e92f46ed9962c9be01f73d762
+-- hash: 7b3965e17f09091c9d4a3bdedd075b5d746f3d7c24fc907dde01a2de3168dc85
name: hprox
-version: 0.1.0.2
+version: 0.1.1
synopsis: a lightweight HTTP proxy server, and more
description: Please see the README on GitHub at <https://github.com/bjin/hprox#readme>
category: Web
@@ -35,6 +35,7 @@ executable hprox
main-is: Main.hs
other-modules:
HProx
+ Paths_hprox
hs-source-dirs:
src
ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N
@@ -48,6 +49,7 @@ executable hprox
, conduit >=1.3
, conduit-extra >=1.3
, http-client >=0.5
+ , http-client-tls >=0.3.4
, http-reverse-proxy >=0.4.0
, http-types >=0.12
, optparse-applicative >=0.14
diff --git a/src/HProx.hs b/src/HProx.hs
index 87252ef..c323045 100644
--- a/src/HProx.hs
+++ b/src/HProx.hs
@@ -93,54 +93,10 @@ parseHostPortWithDefault defaultPort hostPort =
fromMaybe (hostPort, defaultPort) $ parseHostPort hostPort
isProxyHeader :: HT.HeaderName -> Bool
-isProxyHeader k
- | BS.length bs <= 4 = False
- | c0 /= 112 && c0 /= 80 = False -- 'p'
- | c1 /= 114 && c1 /= 82 = False -- 'r'
- | c2 /= 111 && c2 /= 79 = False -- 'o'
- | c3 /= 120 && c3 /= 88 = False -- 'x'
- | c4 /= 121 && c4 /= 89 = False -- 'y'
- | otherwise = True
- where
- bs = CI.original k
- idx = BS.index bs
-
- c0 = idx 0
- c1 = idx 1
- c2 = idx 2
- c3 = idx 3
- c4 = idx 4
+isProxyHeader k = "proxy" `BS.isPrefixOf` CI.foldedCase k
isForwardedHeader :: HT.HeaderName -> Bool
-isForwardedHeader k
- | BS.length bs <= 10 = False
- | c0 /= 120 && c0 /= 88 = False -- 'x'
- | c1 /= 45 = False -- '-'
- | c2 /= 102 && c2 /= 70 = False -- 'f'
- | c3 /= 111 && c3 /= 79 = False -- 'o'
- | c4 /= 114 && c4 /= 82 = False -- 'r'
- | c5 /= 119 && c5 /= 87 = False -- 'w'
- | c6 /= 97 && c6 /= 65 = False -- 'a'
- | c7 /= 114 && c7 /= 82 = False -- 'r'
- | c8 /= 100 && c8 /= 68 = False -- 'd'
- | c9 /= 101 && c9 /= 69 = False -- 'e'
- | ca /= 100 && ca /= 68 = False -- 'd'
- | otherwise = True
- where
- bs = CI.original k
- idx = BS.index bs
-
- c0 = idx 0
- c1 = idx 1
- c2 = idx 2
- c3 = idx 3
- c4 = idx 4
- c5 = idx 5
- c6 = idx 6
- c7 = idx 7
- c8 = idx 8
- c9 = idx 9
- ca = idx 10
+isForwardedHeader k = "x-forwarded" `BS.isPrefixOf` CI.foldedCase k
isToStripHeader :: HT.HeaderName -> Bool
isToStripHeader h = isProxyHeader h || isForwardedHeader h || h == "X-Real-IP" || h == "X-Scheme"
@@ -193,8 +149,9 @@ reverseProxy pset mgr fallback
isReverseProxy = isJust (revRemote pset)
(revHost, revPort) = parseHostPortWithDefault 80 (fromJust (revRemote pset))
+ revWrapper = if revPort == 443 then WPRModifiedRequestSecure else WPRModifiedRequest
- proxyResponseFor req = WPRModifiedRequest nreq (ProxyDest revHost revPort)
+ proxyResponseFor req = revWrapper nreq (ProxyDest revHost revPort)
where
nreq = req
{ requestHeaders = hdrs
@@ -212,7 +169,7 @@ httpGetProxy pset mgr fallback = waiProxyToSettings (return.proxyResponseFor) se
settings = defaultWaiProxySettings { wpsSetIpHeader = SIHNone }
proxyResponseFor req
- | redirectWebsocket = WPRProxyDest (ProxyDest wsHost wsPort)
+ | redirectWebsocket = wsWrapper (ProxyDest wsHost wsPort)
| not isGetProxy = WPRApplication fallback
| checkAuth pset req = WPRModifiedRequest nreq (ProxyDest host port)
| otherwise = WPRResponse (proxyAuthRequiredResponse pset)
@@ -220,6 +177,7 @@ httpGetProxy pset mgr fallback = waiProxyToSettings (return.proxyResponseFor) se
isWebsocket = wpsUpgradeToRaw defaultWaiProxySettings req
redirectWebsocket = isWebsocket && isJust (wsRemote pset)
(wsHost, wsPort) = parseHostPortWithDefault 80 (fromJust (wsRemote pset))
+ wsWrapper = if wsPort == 443 then WPRProxyDestSecure else WPRProxyDest
notCONNECT = requestMethod req /= "CONNECT"
rawPath = rawPathInfo req
diff --git a/src/Main.hs b/src/Main.hs
index 0ea7a3c..b19cef5 100644
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -7,7 +7,7 @@ module Main where
import qualified Data.ByteString.Char8 as BS8
import Data.String (fromString)
-import qualified Network.HTTP.Client as HC
+import Network.HTTP.Client.TLS (newTlsManager)
import Network.TLS as TLS
import Network.Wai.Handler.Warp (HostPreference, defaultSettings,
runSettings, setBeforeMainLoop,
@@ -22,10 +22,12 @@ import System.Posix.User (UserEntry (..),
import Data.Maybe
import Data.Monoid ((<>))
+import Data.Version (showVersion)
import Options.Applicative
import HProx (ProxySettings (..), dumbApp,
forceSSL, httpProxy, reverseProxy)
+import Paths_hprox (version)
data Opts = Opts
{ _bind :: Maybe HostPreference
@@ -52,12 +54,15 @@ splitBy c (x:xs)
| otherwise = let y:ys = splitBy c xs in (x:y):ys
parser :: ParserInfo Opts
-parser = info (helper <*> opts) fullDesc
+parser = info (helper <*> ver <*> opts) (fullDesc <> progDesc desc)
where
parseSSL s = case splitBy ':' s of
[host, cert, key] -> Right (host, CertFile cert key)
_ -> Left "invalid format for ssl certificates"
+ desc = "a lightweight HTTP proxy server, and more"
+ ver = infoOption (showVersion version) (long "version" <> help "show version")
+
opts = Opts <$> bind
<*> (fromMaybe 3000 <$> port)
<*> ssl
@@ -70,7 +75,7 @@ parser = info (helper <*> opts) fullDesc
( long "bind"
<> short 'b'
<> metavar "bind_ip"
- <> help "the ip address to bind on (default: all interfaces)")
+ <> help "ip address to bind on (default: all interfaces)")
port = optional $ option auto
( long "port"
@@ -82,7 +87,7 @@ parser = info (helper <*> opts) fullDesc
( long "tls"
<> short 's'
<> metavar "hostname:cerfile:keyfile"
- <> help "enable TLS and specify a domain and associated TLS certificate (can be used multiple times for multiple domains)")
+ <> help "enable TLS and specify a domain and associated TLS certificate (can be specified multiple times for multiple domains)")
user = optional $ strOption
( long "user"
@@ -94,17 +99,17 @@ parser = info (helper <*> opts) fullDesc
( long "auth"
<> short 'a'
<> metavar "userpass.txt"
- <> help "password file for proxy authentication (plain text file with lines each containaing a colon separated user/password pair)")
+ <> help "password file for proxy authentication (plain text file with lines each containing a colon separated user/password pair)")
ws = optional $ strOption
( long "ws"
- <> metavar "remote-host:80"
- <> help "remote host to handle websocket requests (http server only)")
+ <> metavar "remote-host:port"
+ <> help "remote host to handle websocket requests (port 443 indicates HTTPS remote server)")
rev = optional $ strOption
( long "rev"
- <> metavar "remote-host:80"
- <> help "remote host for revere proxy (http server only)")
+ <> metavar "remote-host:port"
+ <> help "remote host for reverse proxy (port 443 indicates HTTPS remote server)")
setuid :: String -> IO ()
@@ -144,7 +149,7 @@ main = do
pauth <- case _auth opts of
Nothing -> return Nothing
Just f -> Just . flip elem . filter (isJust . BS8.elemIndex ':') . BS8.lines <$> BS8.readFile f
- manager <- HC.newManager HC.defaultManagerSettings
+ manager <- newTlsManager
let pset = ProxySettings pauth Nothing (BS8.pack <$> _ws opts) (BS8.pack <$> _rev opts)
proxy = (if isSSL then forceSSL else id) $ gzip def $ httpProxy pset manager $ reverseProxy pset manager dumbApp