package http3

import (
	
	
	
	
	
	
	
	

	
	

	
)

// The HTTPStreamer allows taking over a HTTP/3 stream. The interface is implemented by the http.ResponseWriter.
// When a stream is taken over, it's the caller's responsibility to close the stream.
type HTTPStreamer interface {
	HTTPStream() *Stream
}

const maxSmallResponseSize = 4096

type responseWriter struct {
	str *Stream

	conn     *rawConn
	header   http.Header
	trailers map[string]struct{}
	buf      []byte
	status   int // status code passed to WriteHeader

	// for responses smaller than maxSmallResponseSize, we buffer calls to Write,
	// and automatically add the Content-Length header
	smallResponseBuf []byte

	contentLen     int64 // if handler set valid Content-Length header
	numWritten     int64 // bytes written
	headerComplete bool  // set once WriteHeader is called with a status code >= 200
	headerWritten  bool  // set once the response header has been serialized to the stream
	isHead         bool
	trailerWritten bool // set once the response trailers has been serialized to the stream

	hijacked bool // set on HTTPStream is called

	logger *slog.Logger
}

var (
	_ http.ResponseWriter = &responseWriter{}
	_ http.Flusher        = &responseWriter{}
	_ Settingser          = &responseWriter{}
	_ HTTPStreamer        = &responseWriter{}
	// make sure that we implement (some of the) methods used by the http.ResponseController
	_ interface {
		SetReadDeadline(time.Time) error
		SetWriteDeadline(time.Time) error
		Flush()
		FlushError() error
	} = &responseWriter{}
)

func newResponseWriter( *Stream,  *rawConn,  bool,  *slog.Logger) *responseWriter {
	return &responseWriter{
		str:    ,
		conn:   ,
		header: http.Header{},
		buf:    make([]byte, frameHeaderLen),
		isHead: ,
		logger: ,
	}
}

func ( *responseWriter) () http.Header {
	return .header
}

func ( *responseWriter) ( int) {
	if .headerComplete {
		return
	}

	// http status must be 3 digits
	if  < 100 ||  > 999 {
		panic(fmt.Sprintf("invalid WriteHeader code %v", ))
	}
	.status = 

	// immediately write 1xx headers
	if  < 200 {
		.writeHeader()
		return
	}

	// We're done with headers once we write a status >= 200.
	.headerComplete = true
	// Add Date header.
	// This is what the standard library does.
	// Can be disabled by setting the Date header to nil.
	if ,  := .header["Date"]; ! {
		.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	}
	// Content-Length checking
	// use ParseUint instead of ParseInt, as negative values are invalid
	if  := .header.Get("Content-Length");  != "" {
		if ,  := strconv.ParseUint(, 10, 63);  == nil {
			.contentLen = int64()
		} else {
			// emit a warning for malformed Content-Length and remove it
			 := .logger
			if  == nil {
				 = slog.Default()
			}
			.Error("Malformed Content-Length", "value", )
			.header.Del("Content-Length")
		}
	}
}

func ( *responseWriter) ( []byte) {
	// If no content type, apply sniffing algorithm to body.
	// We can't use `w.header.Get` here since if the Content-Type was set to nil, we shouldn't do sniffing.
	,  := .header["Content-Type"]

	// If the Content-Encoding was set and is non-blank, we shouldn't sniff the body.
	 := .header.Get("Content-Encoding") != ""
	if ! && ! && len() > 0 {
		.header.Set("Content-Type", http.DetectContentType())
	}
}

func ( *responseWriter) ( []byte) (int, error) {
	 := bodyAllowedForStatus(.status)
	if !.headerComplete {
		.sniffContentType()
		.WriteHeader(http.StatusOK)
		 = true
	}
	if ! {
		return 0, http.ErrBodyNotAllowed
	}

	.numWritten += int64(len())
	if .contentLen != 0 && .numWritten > .contentLen {
		return 0, http.ErrContentLength
	}

	if .isHead {
		return len(), nil
	}

	if !.headerWritten {
		// Buffer small responses.
		// This allows us to automatically set the Content-Length field.
		if len(.smallResponseBuf)+len() < maxSmallResponseSize {
			.smallResponseBuf = append(.smallResponseBuf, ...)
			return len(), nil
		}
	}
	return .doWrite()
}

func ( *responseWriter) ( []byte) (int, error) {
	if !.headerWritten {
		.sniffContentType(.smallResponseBuf)
		if  := .writeHeader(.status);  != nil {
			return 0, maybeReplaceError()
		}
		.headerWritten = true
	}

	 := uint64(len(.smallResponseBuf) + len())
	if  == 0 {
		return 0, nil
	}
	 := &dataFrame{Length: }
	.buf = .buf[:0]
	.buf = .Append(.buf)
	if .str.qlogger != nil {
		.str.qlogger.RecordEvent(qlog.FrameCreated{
			StreamID: .str.StreamID(),
			Raw:      qlog.RawInfo{Length: len(.buf) + int(), PayloadLength: int()},
			Frame:    qlog.Frame{Frame: qlog.DataFrame{}},
		})
	}
	if ,  := .str.writeUnframed(.buf);  != nil {
		return 0, maybeReplaceError()
	}
	if len(.smallResponseBuf) > 0 {
		if ,  := .str.writeUnframed(.smallResponseBuf);  != nil {
			return 0, maybeReplaceError()
		}
		.smallResponseBuf = nil
	}
	var  int
	if len() > 0 {
		var  error
		,  = .str.writeUnframed()
		if  != nil {
			return , maybeReplaceError()
		}
	}
	return , nil
}

func ( *responseWriter) ( int) error {
	var  []qlog.HeaderField // only used for qlog
	var  bytes.Buffer
	 := qpack.NewEncoder(&)
	if  := .WriteField(qpack.HeaderField{Name: ":status", Value: strconv.Itoa()});  != nil {
		return 
	}
	if .str.qlogger != nil {
		 = append(, qlog.HeaderField{Name: ":status", Value: strconv.Itoa()})
	}

	// Handle trailer fields
	if ,  := .header["Trailer"];  {
		for ,  := range  {
			for ,  := range strings.Split(, ",") {
				// We need to convert to the canonical header key value here because this will be called when using
				// headers.Add or headers.Set.
				 = textproto.CanonicalMIMEHeaderKey(strings.TrimSpace())
				.declareTrailer()
			}
		}
	}

	for ,  := range .header {
		if ,  := .trailers[];  {
			continue
		}
		// Ignore "Trailer:" prefixed headers
		if strings.HasPrefix(, http.TrailerPrefix) {
			continue
		}
		for  := range  {
			 := strings.ToLower()
			 := []
			if  := .WriteField(qpack.HeaderField{Name: , Value: });  != nil {
				return 
			}
			if .str.qlogger != nil {
				 = append(, qlog.HeaderField{Name: , Value: })
			}
		}
	}

	 := make([]byte, 0, frameHeaderLen+.Len())
	 = (&headersFrame{Length: uint64(.Len())}).Append()
	 = append(, .Bytes()...)

	if .str.qlogger != nil {
		qlogCreatedHeadersFrame(.str.qlogger, .str.StreamID(), len(), .Len(), )
	}

	,  := .str.writeUnframed()
	return 
}

func ( *responseWriter) () error {
	if !.headerComplete {
		.WriteHeader(http.StatusOK)
	}
	,  := .doWrite(nil)
	return 
}

func ( *responseWriter) () {
	if .trailerWritten {
		return
	}
	if  := .writeTrailers();  != nil {
		.logger.Debug("could not write trailers", "error", )
	}
}

func ( *responseWriter) () {
	if  := .FlushError();  != nil {
		if .logger != nil {
			.logger.Debug("could not flush to stream", "error", )
		}
	}
}

// declareTrailer adds a trailer to the trailer list, while also validating that the trailer has a
// valid name.
func ( *responseWriter) ( string) {
	if !httpguts.ValidTrailerHeader() {
		// Forbidden by RFC 9110, section 6.5.1.
		.logger.Debug("ignoring invalid trailer", slog.String("header", ))
		return
	}
	if .trailers == nil {
		.trailers = make(map[string]struct{})
	}
	.trailers[] = struct{}{}
}

// writeTrailers will write trailers to the stream if there are any.
func ( *responseWriter) () error {
	// promote headers added via "Trailer:" convention as trailers, these can be added after
	// streaming the status/headers have been written.
	for  := range .header {
		if strings.HasPrefix(, http.TrailerPrefix) {
			.declareTrailer()
		}
	}

	if len(.trailers) == 0 {
		return nil
	}

	 := make(http.Header, len(.trailers))
	for  := range .trailers {
		if ,  := .header[];  {
			[strings.TrimPrefix(, http.TrailerPrefix)] = 
		}
	}

	,  := writeTrailers(.str.datagramStream, , .str.StreamID(), .str.qlogger)
	if  {
		.trailerWritten = true
	}
	return 
}

func ( *responseWriter) () *Stream {
	.hijacked = true
	.Flush()
	return .str
}

func ( *responseWriter) () bool { return .hijacked }

func ( *responseWriter) () <-chan struct{} {
	return .conn.ReceivedSettings()
}

func ( *responseWriter) () *Settings {
	return .conn.Settings()
}

func ( *responseWriter) ( time.Time) error {
	return .str.SetReadDeadline()
}

func ( *responseWriter) ( time.Time) error {
	return .str.SetWriteDeadline()
}

// copied from http2/http2.go
// bodyAllowedForStatus reports whether a given response status code
// permits a body. See RFC 2616, section 4.4.
func bodyAllowedForStatus( int) bool {
	switch {
	case  >= 100 &&  <= 199:
		return false
	case  == http.StatusNoContent:
		return false
	case  == http.StatusNotModified:
		return false
	}
	return true
}