github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/mime.go (about)

     1  // Package cutil - Content managed by Project Forge, see [projectforge.md] for details.
     2  package cutil
     3  
     4  import (
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  	"gopkg.in/yaml.v2"
    12  
    13  	"github.com/kyleu/dbaudit/app"
    14  	"github.com/kyleu/dbaudit/app/util"
    15  )
    16  
    17  const (
    18  	mimeCSV   = "text/csv"
    19  	mimeDebug = "text/plain"
    20  	mimeJSON  = "application/json"
    21  	mimeXML   = "text/xml"
    22  	mimeYAML  = "application/x-yaml"
    23  
    24  	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
    25  	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
    26  	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
    27  	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
    28  	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
    29  	HeaderCacheControl                  = "Cache-Control"
    30  	HeaderContentType                   = "Content-Type"
    31  	HeaderReferer                       = "Referer"
    32  )
    33  
    34  var (
    35  	AllowedRequestHeaders  = "*"
    36  	AllowedResponseHeaders = "*"
    37  )
    38  
    39  func WriteCORS(w http.ResponseWriter) {
    40  	h := w.Header()
    41  	setIfEmpty := func(k string, v string) {
    42  		if x := h.Get(k); x == "" {
    43  			h.Set(k, v)
    44  		}
    45  	}
    46  	setIfEmpty(HeaderAccessControlAllowHeaders, AllowedRequestHeaders)
    47  	setIfEmpty(HeaderAccessControlAllowMethods, "GET,POST,DELETE,PUT,PATCH,OPTIONS,HEAD")
    48  	if x := string(h.Get(HeaderReferer)); x == "" {
    49  		setIfEmpty(HeaderAccessControlAllowOrigin, "*")
    50  	} else {
    51  		u, err := url.Parse(x)
    52  		if err == nil {
    53  			o := u.Hostname()
    54  			if u.Port() != "" {
    55  				o += ":" + u.Port()
    56  			}
    57  			sch := u.Scheme
    58  			if strings.Contains(o, ".network") {
    59  				sch = "https"
    60  			}
    61  			setIfEmpty(HeaderAccessControlAllowOrigin, sch+"://"+o)
    62  		} else {
    63  			setIfEmpty(HeaderAccessControlAllowOrigin, "*")
    64  		}
    65  	}
    66  	setIfEmpty(HeaderAccessControlAllowCredentials, util.BoolTrue)
    67  	setIfEmpty(HeaderAccessControlExposeHeaders, AllowedResponseHeaders)
    68  }
    69  
    70  func RespondDebug(w http.ResponseWriter, r *http.Request, as *app.State, filename string, ps *PageState) (string, error) {
    71  	return RespondJSON(w, filename, RequestCtxToMap(w, r, as, ps))
    72  }
    73  
    74  func RespondCSV(w http.ResponseWriter, filename string, body any) (string, error) {
    75  	b, err := util.ToCSVBytes(body)
    76  	if err != nil {
    77  		return "", err
    78  	}
    79  	return RespondMIME(filename, mimeCSV, "csv", b, w)
    80  }
    81  
    82  func RespondJSON(w http.ResponseWriter, filename string, body any) (string, error) {
    83  	b := util.ToJSONBytes(body, true)
    84  	return RespondMIME(filename, mimeJSON, "json", b, w)
    85  }
    86  
    87  type XMLResponse struct {
    88  	Result any `xml:"result"`
    89  }
    90  
    91  func RespondXML(w http.ResponseWriter, filename string, body any) (string, error) {
    92  	b, err := util.ToXMLBytes(body, true)
    93  	if err != nil {
    94  		return "", errors.Wrapf(err, "can't serialize response of type [%T] to XML", body)
    95  	}
    96  	return RespondMIME(filename, mimeXML, "xml", b, w)
    97  }
    98  
    99  func RespondYAML(w http.ResponseWriter, filename string, body any) (string, error) {
   100  	b, err := yaml.Marshal(body)
   101  	if err != nil {
   102  		return "", err
   103  	}
   104  	return RespondMIME(filename, mimeYAML, "yaml", b, w)
   105  }
   106  
   107  func RespondMIME(filename string, mime string, ext string, ba []byte, w http.ResponseWriter) (string, error) {
   108  	h := w.Header()
   109  	h.Set(HeaderContentType, mime+"; charset=UTF-8")
   110  	if filename != "" {
   111  		if !strings.HasSuffix(filename, "."+ext) {
   112  			filename = filename + "." + ext
   113  		}
   114  		h.Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, filename))
   115  	}
   116  	WriteCORS(w)
   117  	if len(ba) == 0 {
   118  		return "", errors.New("no bytes available to write")
   119  	}
   120  	if _, err := w.Write(ba); err != nil {
   121  		return "", errors.Wrap(err, "cannot write to response")
   122  	}
   123  
   124  	return "", nil
   125  }
   126  
   127  func GetContentType(r *http.Request) string {
   128  	ret := r.Header.Get(HeaderContentType)
   129  	if idx := strings.Index(ret, ";"); idx > -1 {
   130  		ret = ret[0:idx]
   131  	}
   132  	t := r.URL.Query().Get("t")
   133  	switch t {
   134  	case "debug":
   135  		return mimeDebug
   136  	case "csv":
   137  		return mimeCSV
   138  	case "json":
   139  		return mimeJSON
   140  	case "xml":
   141  		return mimeXML
   142  	case "yaml", "yml":
   143  		return mimeYAML
   144  	default:
   145  		return strings.TrimSpace(ret)
   146  	}
   147  }
   148  
   149  func IsContentTypeCSV(c string) bool {
   150  	return c == mimeCSV
   151  }
   152  
   153  func IsContentTypeDebug(c string) bool {
   154  	return c == mimeDebug
   155  }
   156  
   157  func IsContentTypeJSON(c string) bool {
   158  	return c == mimeJSON || c == "text/json"
   159  }
   160  
   161  func IsContentTypeXML(c string) bool {
   162  	return c == "application/xml" || c == mimeXML
   163  }
   164  
   165  func IsContentTypeYAML(c string) bool {
   166  	return c == mimeYAML || c == "text/yaml"
   167  }