// tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
//              manager, WARC/geminispace browser
// Copyright (C) 2021-2026 Sergey Matveev <stargrave@stargrave.org>
//
// 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
// the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package tofuproxy

import (
	"crypto"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net/http"
	"time"

	ttls "go.stargrave.org/tofuproxy/tls"
)

var (
	TLSNextProtoS = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
	CACert        *x509.Certificate
	CAPrv         crypto.PrivateKey
)

type Handler struct{}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodConnect {
		roundTrip(w, req)
		return
	}
	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Fatalln("no hijacking")
	}
	conn, _, err := hj.Hijack()
	if err != nil {
		log.Fatalln(err)
	}
	defer conn.Close()
	fmt.Fprintf(
		conn, "%s %d %s\r\n\r\n",
		req.Proto, http.StatusOK, http.StatusText(http.StatusOK),
	)
	host, _, _ := ttls.SplitHostPort(req.Host)
	hostCertsM.Lock()
	keypair, ok := hostCerts[host]
	if !ok || !keypair.cert.NotAfter.After(time.Now().Add(time.Hour)) {
		keypair = newX509Keypair(host, CACert, CAPrv)
		hostCerts[host] = keypair
	}
	hostCertsM.Unlock()
	tlsConn := tls.Server(conn, &tls.Config{
		Certificates: []tls.Certificate{{
			Certificate: [][]byte{keypair.cert.Raw},
			PrivateKey:  keypair.prv,
		}},
	})
	if err = tlsConn.Handshake(); err != nil {
		log.Printf("TLS error %s: %+v\n", host, err)
		return
	}
	srv := http.Server{
		Handler:      &HTTPSHandler{host: req.Host},
		TLSNextProto: TLSNextProtoS,
	}
	err = srv.Serve(&SingleListener{conn: tlsConn})
	if err != nil {
		if _, ok := err.(AlreadyAccepted); !ok {
			log.Printf("TLS serve error %s: %+v\n", host, err)
			return
		}
	}
}

type HTTPSHandler struct {
	host string
}

func (h *HTTPSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	req.URL.Scheme = "https"
	req.URL.Host = h.host
	roundTrip(w, req)
}
