// Package badproxy implements misbehaving proxies. We have a single
// CensoringProxy that exports two misbehaving endpoints. Each endpoint
// implements a different proxy-censorsing technique. The first one
// reads some bytes from the connection then closes the connection. The
// other instead replies with a self signed x509 certificate.
package badproxy

import (
	"context"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"io"
	"net"
	"strings"
	"time"

	"github.com/google/martian/v3/mitm"
	"github.com/ooni/probe-cli/v3/internal/netxlite"
)

// CensoringProxy is a proxy that does not behave correctly.
type CensoringProxy struct {
	mitmNewAuthority func(
		name string, organization string,
		validity time.Duration,
	) (*x509.Certificate, *rsa.PrivateKey, error)

	mitmNewConfig func(
		ca *x509.Certificate, privateKey interface{},
	) (*mitm.Config, error)

	tlsListen func(
		network string, laddr string, config *tls.Config,
	) (net.Listener, error)
}

// NewCensoringProxy creates a new instance of a misbehaving proxy.
func NewCensoringProxy() *CensoringProxy {
	return &CensoringProxy{
		mitmNewAuthority: mitm.NewAuthority,
		mitmNewConfig:    mitm.NewConfig,
		tlsListen:        tls.Listen,
	}
}

func (p *CensoringProxy) serve(conn net.Conn) {
	deadline := time.Now().Add(250 * time.Millisecond)
	conn.SetDeadline(deadline)
	// To simulate the case where the proxy isn't willing to forward our
	// traffic, we close the connection (1) right after the handshake for
	// TLS connections and (2) reasonably after we've received the HTTP
	// request for cleartext connections. This may break in several cases
	// but is good enough approximation of these bad proxies for now.
	if tlsconn, ok := conn.(*tls.Conn); ok {
		tlsconn.Handshake()
	} else {
		const maxread = 1 << 17
		reader := io.LimitReader(conn, maxread)
		netxlite.ReadAllContext(context.Background(), reader)
	}
	conn.Close()
}

func (p *CensoringProxy) run(listener net.Listener) {
	for {
		conn, err := listener.Accept()
		if err != nil && strings.Contains(
			err.Error(), "use of closed network connection") {
			return
		}
		if err == nil {
			// It's difficult to make accept fail, so restructure
			// the code such that we enter into the happy path
			go p.serve(conn)
		}
	}
}

// Start starts the misbehaving proxy for TCP. This endpoint will read some
// bytes from the request and then close the connection. This behaviour is
// implemented by a bunch of censoring proxy around the world. Usually such
// proxies only close the connection with offending SNIs/Host headers.
func (p *CensoringProxy) Start(address string) (net.Listener, error) {
	listener, err := net.Listen("tcp", address)
	if err != nil {
		return nil, err
	}
	go p.run(listener)
	return listener, nil
}

// StartTLS starts the misbehaving proxy for TLS. This endpoint will return
// to the client a self signed certificate. Thus, it models the case where a
// MITM forces users to accept a rogue certificate. After sending such a
// certificate, this proxy will close the TCP connection.
func (p *CensoringProxy) StartTLS(address string) (net.Listener, *x509.Certificate, error) {
	cert, privkey, err := p.mitmNewAuthority(
		"jafar", "OONI", 24*time.Hour,
	)
	if err != nil {
		return nil, nil, err
	}
	config, err := p.mitmNewConfig(cert, privkey)
	if err != nil {
		return nil, nil, err
	}
	listener, err := p.tlsListen("tcp", address, config.TLS())
	if err != nil {
		return nil, nil, err
	}
	go p.run(listener)
	return listener, cert, nil
}
