+/*
+GoCheese -- Python private package repository and caching proxy
+Copyright (C) 2019-2021 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 main
+
+import (
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "sync"
+ "time"
+)
+
+var (
+ aLongTimeAgo = time.Unix(1, 0)
+ UCSPIJob sync.WaitGroup
+)
+
+type UCSPIAddr struct {
+ ip string
+ port string
+}
+
+func (addr *UCSPIAddr) Network() string { return "tcp" }
+
+func (addr *UCSPIAddr) String() string { return addr.ip + ":" + addr.port }
+
+type UCSPIConn struct {
+ eof chan struct{}
+}
+
+type ReadResult struct {
+ n int
+ err error
+}
+
+func (conn *UCSPIConn) Read(b []byte) (int, error) {
+ c := make(chan ReadResult)
+ go func() {
+ n, err := os.Stdin.Read(b)
+ c <- ReadResult{n, err}
+ }()
+ select {
+ case res := <-c:
+ return res.n, res.err
+ case <-conn.eof:
+ return 0, io.EOF
+ }
+}
+
+func (conn *UCSPIConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) }
+
+func (conn *UCSPIConn) Close() error {
+ if err := os.Stdin.Close(); err != nil {
+ return err
+ }
+ return os.Stdout.Close()
+}
+
+func (conn *UCSPIConn) LocalAddr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")}
+}
+
+func (conn *UCSPIConn) RemoteAddr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPREMOTEIP"), port: os.Getenv("TCPREMOTEPORT")}
+}
+
+func (conn *UCSPIConn) SetDeadline(t time.Time) error {
+ if err := os.Stdin.SetReadDeadline(t); err != nil {
+ return err
+ }
+ return os.Stdout.SetWriteDeadline(t)
+}
+
+func (conn *UCSPIConn) SetReadDeadline(t time.Time) error {
+ // An ugly hack to forcefully terminate pending read.
+ // net/http calls SetReadDeadline(aLongTimeAgo), but file
+ // descriptors are not capable to exit immediately that way.
+ if t.Equal(aLongTimeAgo) {
+ conn.eof <- struct{}{}
+ }
+ return os.Stdin.SetReadDeadline(t)
+}
+
+func (conn *UCSPIConn) SetWriteDeadline(t time.Time) error {
+ return os.Stdout.SetWriteDeadline(t)
+}
+
+type UCSPI struct{ accepted bool }
+
+type UCSPIAlreadyAccepted struct{}
+
+func (u UCSPIAlreadyAccepted) Error() string {
+ return "already accepted"
+}
+
+func (ucspi *UCSPI) Accept() (net.Conn, error) {
+ if ucspi.accepted {
+ return nil, UCSPIAlreadyAccepted{}
+ }
+ ucspi.accepted = true
+ conn := UCSPIConn{eof: make(chan struct{}, 1)}
+ return &conn, nil
+}
+
+func (ucspi *UCSPI) Close() error {
+ return nil
+}
+
+func (ucspi *UCSPI) Addr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")}
+}
+
+func connStater(conn net.Conn, connState http.ConnState) {
+ switch connState {
+ case http.StateNew:
+ UCSPIJob.Add(1)
+ case http.StateClosed:
+ UCSPIJob.Done()
+ }
+}