130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
// Package data provides operations to deal with arbitrary data and MIME types.
|
|
package data
|
|
|
|
import "io"
|
|
import "bytes"
|
|
import "strings"
|
|
|
|
// Data represents arbitrary polymorphic data that can be used for data transfer
|
|
// between applications.
|
|
type Data interface {
|
|
// Convert converts the data to the specified MIME type and returns it.
|
|
// If the type is not supported, this behavior will return nil, and
|
|
// false for ok. Note that Convert may be called multiple times for the
|
|
// same MIME type, so it must not return the same reader.
|
|
Convert (Mime) (reader io.ReadSeekCloser, ok bool)
|
|
|
|
// Supported returns a slice of MIME types that Convert can accept.
|
|
Supported () []Mime
|
|
}
|
|
|
|
// Mime represents a MIME type.
|
|
type Mime struct {
|
|
// Type is the first half of the MIME type, and Subtype is the second
|
|
// half. The separating slash is not included in either. For example,
|
|
// text/html becomes:
|
|
// Mime { Type: "text", Subtype: "html" }
|
|
// The subtype is stored here including the tree and the suffix.
|
|
Type, Subtype string
|
|
|
|
// Charset is an optional field applicable to text types that specifies
|
|
// the character encoding of the data. If empty, UTF-8 is conventionally
|
|
// assumed because it's the only text encoding worth using.
|
|
Charset string
|
|
}
|
|
|
|
// M is shorthand for creating a MIME type.
|
|
func M (ty, subtype string) Mime {
|
|
return Mime {
|
|
Type: strings.ToLower(ty),
|
|
Subtype: strings.ToLower(subtype),
|
|
}
|
|
}
|
|
|
|
// ParseMime parses a MIME type from text.
|
|
func ParseMime (text string) Mime {
|
|
ty, subty, _ := strings.Cut(text, "/")
|
|
subty, parameters, _ := strings.Cut(subty, ";")
|
|
mime := M(ty, subty)
|
|
for _, parameter := range strings.Split(parameters, " ") {
|
|
if parameter == "" { continue }
|
|
key, val, _ := strings.Cut(parameter, "=")
|
|
// TODO handle things like quoted values
|
|
val = strings.TrimSpace(val)
|
|
switch strings.TrimSpace(strings.ToLower(key)) {
|
|
case "charset":
|
|
mime.Charset = val
|
|
}
|
|
}
|
|
return mime
|
|
}
|
|
|
|
// MimePlain returns the MIME type of plain text.
|
|
func MimePlain () Mime { return Mime { Type: "text", Subtype: "plain" } }
|
|
|
|
// MimeFile returns the MIME type of a file path/URI.
|
|
func MimeFile () Mime { return Mime { Type: "text", Subtype: "uri-list" } }
|
|
|
|
// String returns the string representation of the MIME type.
|
|
func (mime Mime) String () string {
|
|
out := mime.Type + "/" + mime.Subtype
|
|
if mime.Charset != "" {
|
|
out += "; charset=" + mime.Charset
|
|
}
|
|
return out
|
|
}
|
|
|
|
// FromText returns plain text Data given a string.
|
|
func FromText (text string) Data {
|
|
return FromBytes(MimePlain(), []byte(text))
|
|
}
|
|
|
|
type byteReadCloser struct { *bytes.Reader }
|
|
func (byteReadCloser) Close () error { return nil }
|
|
|
|
type bytesData struct {
|
|
mime Mime
|
|
buffer []byte
|
|
}
|
|
|
|
// FromBytes constructs a Data given a buffer and a mime type.
|
|
func FromBytes (mime Mime, buffer []byte) Data {
|
|
return bytesData {
|
|
mime: mime,
|
|
buffer: buffer,
|
|
}
|
|
}
|
|
|
|
func (bytesDat bytesData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
|
|
if mime != bytesDat.mime { return nil, false }
|
|
return byteReadCloser { bytes.NewReader(bytesDat.buffer) }, true
|
|
}
|
|
|
|
func (bytesDat bytesData) Supported () []Mime {
|
|
return []Mime { bytesDat.mime }
|
|
}
|
|
|
|
type mergedData []Data
|
|
|
|
func (merged mergedData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
|
|
for _, individual := range merged {
|
|
if reader, ok := individual.Convert(mime); ok {
|
|
return reader, ok
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (merged mergedData) Supported () (supported []Mime) {
|
|
for _, individual := range merged {
|
|
supported = append(individual.Supported(), supported...)
|
|
}
|
|
return supported
|
|
}
|
|
|
|
// Merge combines several Datas together. If multiple Datas provide a reader for
|
|
// the same mime type, the ones first in the list will take precedence.
|
|
func Merge (individual ...Data) (combined Data) {
|
|
return mergedData(individual)
|
|
}
|