github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/pkg/assetserver/webview/responsewriter_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package webview
     5  
     6  /*
     7  #cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
     8  
     9  #include "gtk/gtk.h"
    10  #include "webkit2/webkit2.h"
    11  #include "gio/gunixinputstream.h"
    12  
    13  */
    14  import "C"
    15  import (
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"os"
    20  	"strconv"
    21  	"syscall"
    22  	"unsafe"
    23  )
    24  
    25  type responseWriter struct {
    26  	req *C.WebKitURISchemeRequest
    27  
    28  	header      http.Header
    29  	wroteHeader bool
    30  	finished    bool
    31  
    32  	w    io.WriteCloser
    33  	wErr error
    34  }
    35  
    36  func (rw *responseWriter) Header() http.Header {
    37  	if rw.header == nil {
    38  		rw.header = http.Header{}
    39  	}
    40  	return rw.header
    41  }
    42  
    43  func (rw *responseWriter) Write(buf []byte) (int, error) {
    44  	if rw.finished {
    45  		return 0, errResponseFinished
    46  	}
    47  
    48  	rw.WriteHeader(http.StatusOK)
    49  	if rw.wErr != nil {
    50  		return 0, rw.wErr
    51  	}
    52  	return rw.w.Write(buf)
    53  }
    54  
    55  func (rw *responseWriter) WriteHeader(code int) {
    56  	if rw.wroteHeader || rw.finished {
    57  		return
    58  	}
    59  	rw.wroteHeader = true
    60  
    61  	contentLength := int64(-1)
    62  	if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
    63  		if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
    64  			contentLength = pLen
    65  		}
    66  	}
    67  
    68  	// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
    69  	// read FD is given to the InputStream and will be closed there.
    70  	// Furthermore we especially don't want to have the FD_CLOEXEC
    71  	rFD, w, err := pipe()
    72  	if err != nil {
    73  		rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
    74  		return
    75  	}
    76  	rw.w = w
    77  
    78  	stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
    79  	defer C.g_object_unref(C.gpointer(stream))
    80  
    81  	if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
    82  		rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
    83  		return
    84  	}
    85  }
    86  
    87  func (rw *responseWriter) Finish() error {
    88  	if !rw.wroteHeader {
    89  		rw.WriteHeader(http.StatusNotImplemented)
    90  	}
    91  
    92  	if rw.finished {
    93  		return nil
    94  	}
    95  	rw.finished = true
    96  	if rw.w != nil {
    97  		rw.w.Close()
    98  	}
    99  	return nil
   100  }
   101  
   102  func (rw *responseWriter) finishWithError(code int, err error) {
   103  	if rw.w != nil {
   104  		rw.w.Close()
   105  		rw.w = &nopCloser{io.Discard}
   106  	}
   107  	rw.wErr = err
   108  
   109  	msg := C.CString(err.Error())
   110  	gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg)
   111  	C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
   112  	C.g_error_free(gerr)
   113  	C.free(unsafe.Pointer(msg))
   114  }
   115  
   116  type nopCloser struct {
   117  	io.Writer
   118  }
   119  
   120  func (nopCloser) Close() error { return nil }
   121  
   122  func pipe() (r int, w *os.File, err error) {
   123  	var p [2]int
   124  	e := syscall.Pipe2(p[0:], 0)
   125  	if e != nil {
   126  		return 0, nil, fmt.Errorf("pipe2: %s", e)
   127  	}
   128  
   129  	return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
   130  }