1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http
6
7import (
8	"fmt"
9	"io"
10	"io/fs"
11)
12
13// fileTransport implements RoundTripper for the 'file' protocol.
14type fileTransport struct {
15	fh fileHandler
16}
17
18// NewFileTransport returns a new [RoundTripper], serving the provided
19// [FileSystem]. The returned RoundTripper ignores the URL host in its
20// incoming requests, as well as most other properties of the
21// request.
22//
23// The typical use case for NewFileTransport is to register the "file"
24// protocol with a [Transport], as in:
25//
26//	t := &http.Transport{}
27//	t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
28//	c := &http.Client{Transport: t}
29//	res, err := c.Get("file:///etc/passwd")
30//	...
31func NewFileTransport(fs FileSystem) RoundTripper {
32	return fileTransport{fileHandler{fs}}
33}
34
35// NewFileTransportFS returns a new [RoundTripper], serving the provided
36// file system fsys. The returned RoundTripper ignores the URL host in its
37// incoming requests, as well as most other properties of the
38// request. The files provided by fsys must implement [io.Seeker].
39//
40// The typical use case for NewFileTransportFS is to register the "file"
41// protocol with a [Transport], as in:
42//
43//	fsys := os.DirFS("/")
44//	t := &http.Transport{}
45//	t.RegisterProtocol("file", http.NewFileTransportFS(fsys))
46//	c := &http.Client{Transport: t}
47//	res, err := c.Get("file:///etc/passwd")
48//	...
49func NewFileTransportFS(fsys fs.FS) RoundTripper {
50	return NewFileTransport(FS(fsys))
51}
52
53func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
54	// We start ServeHTTP in a goroutine, which may take a long
55	// time if the file is large. The newPopulateResponseWriter
56	// call returns a channel which either ServeHTTP or finish()
57	// sends our *Response on, once the *Response itself has been
58	// populated (even if the body itself is still being
59	// written to the res.Body, a pipe)
60	rw, resc := newPopulateResponseWriter()
61	go func() {
62		t.fh.ServeHTTP(rw, req)
63		rw.finish()
64	}()
65	return <-resc, nil
66}
67
68func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {
69	pr, pw := io.Pipe()
70	rw := &populateResponse{
71		ch: make(chan *Response),
72		pw: pw,
73		res: &Response{
74			Proto:      "HTTP/1.0",
75			ProtoMajor: 1,
76			Header:     make(Header),
77			Close:      true,
78			Body:       pr,
79		},
80	}
81	return rw, rw.ch
82}
83
84// populateResponse is a ResponseWriter that populates the *Response
85// in res, and writes its body to a pipe connected to the response
86// body. Once writes begin or finish() is called, the response is sent
87// on ch.
88type populateResponse struct {
89	res          *Response
90	ch           chan *Response
91	wroteHeader  bool
92	hasContent   bool
93	sentResponse bool
94	pw           *io.PipeWriter
95}
96
97func (pr *populateResponse) finish() {
98	if !pr.wroteHeader {
99		pr.WriteHeader(500)
100	}
101	if !pr.sentResponse {
102		pr.sendResponse()
103	}
104	pr.pw.Close()
105}
106
107func (pr *populateResponse) sendResponse() {
108	if pr.sentResponse {
109		return
110	}
111	pr.sentResponse = true
112
113	if pr.hasContent {
114		pr.res.ContentLength = -1
115	}
116	pr.ch <- pr.res
117}
118
119func (pr *populateResponse) Header() Header {
120	return pr.res.Header
121}
122
123func (pr *populateResponse) WriteHeader(code int) {
124	if pr.wroteHeader {
125		return
126	}
127	pr.wroteHeader = true
128
129	pr.res.StatusCode = code
130	pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code))
131}
132
133func (pr *populateResponse) Write(p []byte) (n int, err error) {
134	if !pr.wroteHeader {
135		pr.WriteHeader(StatusOK)
136	}
137	pr.hasContent = true
138	if !pr.sentResponse {
139		pr.sendResponse()
140	}
141	return pr.pw.Write(p)
142}
143