mirror of
https://git.phreedom.club/localhost_frssoft/compy.git
synced 2024-11-27 02:21:34 +00:00
initial commit
This commit is contained in:
commit
343bdd5266
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2015, Barna Csorogi <barnacs@justletit.be>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
21
README
Normal file
21
README
Normal file
|
@ -0,0 +1,21 @@
|
|||
1, Compy
|
||||
Compy is an HTTP/HTTPS proxy with mitm support and basic content compression/transcoding capabilities.
|
||||
|
||||
2, Features:
|
||||
- HTTPS proxy (encrypted connection between client and proxy)
|
||||
- man in the middle support
|
||||
- gzip compression
|
||||
- transcode animated gif to static image
|
||||
- transcode jpeg to desired quality using libjpeg
|
||||
- transcode png
|
||||
- html/css/js minification
|
||||
|
||||
3, Usage
|
||||
See compy --help
|
||||
|
||||
4, Credits
|
||||
https://github.com/pixiv/go-libjpeg
|
||||
https://github.com/tdewolff/minify
|
||||
|
||||
5, License
|
||||
See LICENSE
|
65
compy.go
Normal file
65
compy.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/barnacs/compy/proxy"
|
||||
tc "github.com/barnacs/compy/transcoder"
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
host = flag.String("host", ":9999", "<host:port>")
|
||||
cert = flag.String("cert", "", "proxy cert path")
|
||||
key = flag.String("key", "", "proxy cert key path")
|
||||
ca = flag.String("ca", "", "CA path")
|
||||
caKey = flag.String("cakey", "", "CA key path")
|
||||
|
||||
jpeg = flag.Int("jpeg", 50, "jpeg quality (1-100, 0 to disable)")
|
||||
gif = flag.Bool("gif", true, "transcode gifs into static images")
|
||||
png = flag.Bool("png", true, "transcode png")
|
||||
minify = flag.Bool("minify", false, "minify css/html/js - WARNING: tends to break the web")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
p := proxy.New()
|
||||
|
||||
if *ca != "" {
|
||||
if err := p.EnableMitm(*ca, *caKey); err != nil {
|
||||
fmt.Println("not using mitm:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if *jpeg != 0 {
|
||||
p.AddTranscoder("image/jpeg", tc.NewJpeg(*jpeg))
|
||||
}
|
||||
if *gif {
|
||||
p.AddTranscoder("image/gif", &tc.Gif{})
|
||||
}
|
||||
if *png {
|
||||
p.AddTranscoder("image/png", &tc.Png{})
|
||||
}
|
||||
|
||||
var ttc proxy.Transcoder
|
||||
if *minify {
|
||||
ttc = &tc.Gzip{tc.NewMinifier(), false}
|
||||
} else {
|
||||
ttc = &tc.Gzip{&tc.Identity{}, true}
|
||||
}
|
||||
|
||||
p.AddTranscoder("text/css", ttc)
|
||||
p.AddTranscoder("text/html", ttc)
|
||||
p.AddTranscoder("text/javascript", ttc)
|
||||
p.AddTranscoder("application/javascript", ttc)
|
||||
p.AddTranscoder("application/x-javascript", ttc)
|
||||
|
||||
var err error
|
||||
if *cert != "" {
|
||||
err = p.StartTLS(*host, *cert, *key)
|
||||
} else {
|
||||
err = p.Start(*host)
|
||||
}
|
||||
log.Fatalln(err)
|
||||
}
|
35
proxy/certfaker.go
Normal file
35
proxy/certfaker.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
type certFaker struct {
|
||||
ca *x509.Certificate
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func newCertFaker(caPath, keyPath string) (*certFaker, error) {
|
||||
certs, err := tls.LoadX509KeyPair(caPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ca, err := x509.ParseCertificate(certs.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certFaker{
|
||||
ca: ca,
|
||||
key: certs.PrivateKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cf *certFaker) FakeCert(original *x509.Certificate) (*tls.Certificate, error) {
|
||||
fakeCertData, err := x509.CreateCertificate(nil, original, cf.ca, cf.ca.PublicKey, cf.key)
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{fakeCertData},
|
||||
PrivateKey: cf.key,
|
||||
}, err
|
||||
}
|
45
proxy/mitmlistener.go
Normal file
45
proxy/mitmlistener.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
type mitmListener struct {
|
||||
c chan net.Conn
|
||||
cf *certFaker
|
||||
}
|
||||
|
||||
func newMitmListener(cf *certFaker) *mitmListener {
|
||||
return &mitmListener{
|
||||
c: make(chan net.Conn),
|
||||
cf: cf,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *mitmListener) Accept() (net.Conn, error) {
|
||||
return <-l.c, nil
|
||||
}
|
||||
|
||||
func (l *mitmListener) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *mitmListener) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *mitmListener) Serve(conn net.Conn, host string) (net.Conn, error) {
|
||||
sconn, err := tls.Dial("tcp", host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fakeCert, err := l.cf.FakeCert(sconn.ConnectionState().PeerCertificates[0])
|
||||
if err != nil {
|
||||
sconn.Close()
|
||||
return nil, err
|
||||
}
|
||||
tlsconf := &tls.Config{Certificates: []tls.Certificate{*fakeCert}}
|
||||
l.c <- tls.Server(conn, tlsconf)
|
||||
return sconn, nil
|
||||
}
|
111
proxy/proxy.go
Normal file
111
proxy/proxy.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
transcoders map[string]Transcoder
|
||||
ml *mitmListener
|
||||
}
|
||||
|
||||
type Transcoder interface {
|
||||
Transcode(*ResponseWriter, *ResponseReader) error
|
||||
}
|
||||
|
||||
func New() *Proxy {
|
||||
p := &Proxy{
|
||||
transcoders: make(map[string]Transcoder),
|
||||
ml: nil,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Proxy) EnableMitm(ca, key string) error {
|
||||
cf, err := newCertFaker(ca, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.ml = newMitmListener(cf)
|
||||
go http.Serve(p.ml, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Proxy) AddTranscoder(contentType string, transcoder Transcoder) {
|
||||
p.transcoders[contentType] = transcoder
|
||||
}
|
||||
|
||||
func (p *Proxy) Start(host string) error {
|
||||
return http.ListenAndServe(host, p)
|
||||
}
|
||||
|
||||
func (p *Proxy) StartTLS(host, cert, key string) error {
|
||||
return http.ListenAndServeTLS(host, cert, key, p)
|
||||
}
|
||||
|
||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := p.handle(w, r); err != nil {
|
||||
log.Printf("%s while serving request: %s", err, r.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) handle(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method == "CONNECT" {
|
||||
return p.handleConnect(w, r.Host)
|
||||
}
|
||||
resp, err := forward(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return fmt.Errorf("error forwarding request: %s", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return p.proxyResponse(newResponseWriter(w), newResponseReader(resp))
|
||||
}
|
||||
|
||||
func forward(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Scheme == "" {
|
||||
r.URL.Scheme = "https"
|
||||
}
|
||||
if r.URL.Host == "" {
|
||||
r.URL.Host = r.Host
|
||||
}
|
||||
r.RequestURI = ""
|
||||
return http.DefaultTransport.RoundTrip(r)
|
||||
}
|
||||
|
||||
func (p *Proxy) proxyResponse(w *ResponseWriter, r *ResponseReader) error {
|
||||
w.takeHeaders(r)
|
||||
transcoder, found := p.transcoders[r.ContentType()]
|
||||
if !found {
|
||||
return w.writeFrom(r)
|
||||
}
|
||||
w.setChunked()
|
||||
if err := transcoder.Transcode(w, r); err != nil {
|
||||
return fmt.Errorf("transcoding error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Proxy) handleConnect(w http.ResponseWriter, host string) error {
|
||||
if p.ml == nil {
|
||||
return fmt.Errorf("CONNECT received but mitm is not enabled")
|
||||
}
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return fmt.Errorf("connection cannot be hijacked")
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
conn, _, err := h.Hijack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sconn, err := p.ml.Serve(conn, host)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
sconn.Close() // TODO: reuse this connection for https requests
|
||||
return nil
|
||||
}
|
82
proxy/response.go
Normal file
82
proxy/response.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ResponseReader struct {
|
||||
io.Reader
|
||||
r *http.Response
|
||||
}
|
||||
|
||||
func newResponseReader(r *http.Response) *ResponseReader {
|
||||
return &ResponseReader{
|
||||
Reader: r.Body,
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ResponseReader) ContentType() string {
|
||||
cth := r.Header().Get("Content-Type")
|
||||
ct, _, _ := mime.ParseMediaType(cth)
|
||||
return ct
|
||||
}
|
||||
|
||||
func (r *ResponseReader) Header() http.Header {
|
||||
return r.r.Header
|
||||
}
|
||||
|
||||
func (r *ResponseReader) Request() *http.Request {
|
||||
return r.r.Request
|
||||
}
|
||||
|
||||
type ResponseWriter struct {
|
||||
io.Writer
|
||||
rw http.ResponseWriter
|
||||
statusCode int
|
||||
headersDone bool
|
||||
}
|
||||
|
||||
func newResponseWriter(w http.ResponseWriter) *ResponseWriter {
|
||||
return &ResponseWriter{
|
||||
Writer: w,
|
||||
rw: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) takeHeaders(r *ResponseReader) {
|
||||
for k, v := range r.Header() {
|
||||
for _, v := range v {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(r.r.StatusCode)
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) WriteHeader(s int) {
|
||||
w.statusCode = s
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Header() http.Header {
|
||||
return w.rw.Header()
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) writeFrom(r *ResponseReader) error {
|
||||
w.rw.WriteHeader(r.r.StatusCode)
|
||||
_, err := io.Copy(w.rw, r)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) setChunked() {
|
||||
w.Header().Del("Content-Length")
|
||||
}
|
||||
|
||||
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.headersDone {
|
||||
w.rw.WriteHeader(w.statusCode)
|
||||
w.headersDone = true
|
||||
}
|
||||
return w.Writer.Write(b)
|
||||
}
|
19
transcoder/gif.go
Normal file
19
transcoder/gif.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"image/gif"
|
||||
)
|
||||
|
||||
type Gif struct{}
|
||||
|
||||
func (t *Gif) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
img, err := gif.Decode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = gif.Encode(w, img, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
39
transcoder/gzip.go
Normal file
39
transcoder/gzip.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"github.com/barnacs/compy/proxy"
|
||||
)
|
||||
|
||||
type Gzip struct {
|
||||
proxy.Transcoder
|
||||
SkipGzipped bool
|
||||
}
|
||||
|
||||
func (t *Gzip) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
if t.decompress(r) {
|
||||
gzr, err := gzip.NewReader(r.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
r.Reader = gzr
|
||||
r.Header().Del("Content-Encoding")
|
||||
w.Header().Del("Content-Encoding")
|
||||
}
|
||||
if compress(r) {
|
||||
gzw := gzip.NewWriter(w.Writer)
|
||||
defer gzw.Flush()
|
||||
w.Writer = gzw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
}
|
||||
return t.Transcoder.Transcode(w, r)
|
||||
}
|
||||
|
||||
func (t *Gzip) decompress(r *proxy.ResponseReader) bool {
|
||||
return !t.SkipGzipped && r.Header().Get("Content-Encoding") == "gzip"
|
||||
}
|
||||
|
||||
func compress(r *proxy.ResponseReader) bool {
|
||||
return r.Header().Get("Content-Encoding") == ""
|
||||
}
|
13
transcoder/identity.go
Normal file
13
transcoder/identity.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Identity struct{}
|
||||
|
||||
func (i *Identity) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
_, err := io.Copy(w, r)
|
||||
return err
|
||||
}
|
32
transcoder/jpeg.go
Normal file
32
transcoder/jpeg.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"github.com/pixiv/go-libjpeg/jpeg"
|
||||
)
|
||||
|
||||
type Jpeg struct {
|
||||
decOptions *jpeg.DecoderOptions
|
||||
encOptions *jpeg.EncoderOptions
|
||||
}
|
||||
|
||||
func NewJpeg(quality int) *Jpeg {
|
||||
return &Jpeg{
|
||||
decOptions: &jpeg.DecoderOptions{},
|
||||
encOptions: &jpeg.EncoderOptions{
|
||||
Quality: quality,
|
||||
OptimizeCoding: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Jpeg) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
img, err := jpeg.Decode(r, t.decOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = jpeg.Encode(w, img, t.encOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
34
transcoder/minify.go
Normal file
34
transcoder/minify.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/css"
|
||||
"github.com/tdewolff/minify/html"
|
||||
"github.com/tdewolff/minify/js"
|
||||
"github.com/tdewolff/parse"
|
||||
)
|
||||
|
||||
func init() {
|
||||
parse.MaxBuf *= 8
|
||||
}
|
||||
|
||||
type Minifier struct {
|
||||
m minify.Minify
|
||||
}
|
||||
|
||||
func NewMinifier() *Minifier {
|
||||
m := minify.New()
|
||||
m.AddFunc("text/html", html.Minify)
|
||||
m.AddFunc("text/css", css.Minify)
|
||||
m.AddFunc("text/javascript", js.Minify)
|
||||
m.AddFunc("application/javascript", js.Minify)
|
||||
m.AddFunc("application/x-javascript", js.Minify)
|
||||
return &Minifier{
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Minifier) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
return t.m.Minify(r.ContentType(), w, r)
|
||||
}
|
19
transcoder/png.go
Normal file
19
transcoder/png.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
type Png struct{}
|
||||
|
||||
func (t *Png) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
img, err := png.Decode(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = png.Encode(w, img); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
34
transcoder/text.go
Normal file
34
transcoder/text.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package transcoder
|
||||
|
||||
import (
|
||||
"github.com/barnacs/compy/proxy"
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/css"
|
||||
"github.com/tdewolff/minify/html"
|
||||
"github.com/tdewolff/minify/js"
|
||||
"github.com/tdewolff/parse"
|
||||
)
|
||||
|
||||
func init() {
|
||||
parse.MaxBuf *= 8
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
m minify.Minify
|
||||
}
|
||||
|
||||
func NewText() *Text {
|
||||
m := minify.New()
|
||||
m.AddFunc("text/html", html.Minify)
|
||||
m.AddFunc("text/css", css.Minify)
|
||||
m.AddFunc("text/javascript", js.Minify)
|
||||
m.AddFunc("application/javascript", js.Minify)
|
||||
m.AddFunc("application/x-javascript", js.Minify)
|
||||
return &Text{
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Text) Transcode(w *proxy.ResponseWriter, r *proxy.ResponseReader) error {
|
||||
return t.m.Minify(r.ContentType(), w, r)
|
||||
}
|
Loading…
Reference in a new issue