mirror of
https://github.com/halverneus/static-file-server.git
synced 2024-11-24 09:05:30 +00:00
Merge pull request #63 from halverneus/feature/minimum_tls_version
Minimum TLS Version
This commit is contained in:
commit
46db3fd9f4
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: halverneus
|
@ -1,9 +1,9 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
## GO BUILDER
|
## GO BUILDER
|
||||||
################################################################################
|
################################################################################
|
||||||
FROM golang:1.16.5 as builder
|
FROM golang:1.17.5 as builder
|
||||||
|
|
||||||
ENV VERSION 1.8.4
|
ENV VERSION 1.8.5
|
||||||
ENV BUILD_DIR /build
|
ENV BUILD_DIR /build
|
||||||
|
|
||||||
RUN mkdir -p ${BUILD_DIR}
|
RUN mkdir -p ${BUILD_DIR}
|
||||||
@ -36,5 +36,5 @@ LABEL life.apets.vendor="Halverneus" \
|
|||||||
life.apets.url="https://github.com/halverneus/static-file-server" \
|
life.apets.url="https://github.com/halverneus/static-file-server" \
|
||||||
life.apets.name="Static File Server" \
|
life.apets.name="Static File Server" \
|
||||||
life.apets.description="A tiny static file server" \
|
life.apets.description="A tiny static file server" \
|
||||||
life.apets.version="v1.8.4" \
|
life.apets.version="v1.8.5" \
|
||||||
life.apets.schema-version="1.0"
|
life.apets.schema-version="1.0"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM golang:1.16.5 as builder
|
FROM golang:1.17.5 as builder
|
||||||
|
|
||||||
ENV VERSION 1.8.4
|
ENV VERSION 1.8.5
|
||||||
ENV BUILD_DIR /build
|
ENV BUILD_DIR /build
|
||||||
|
|
||||||
RUN mkdir -p ${BUILD_DIR}
|
RUN mkdir -p ${BUILD_DIR}
|
||||||
@ -21,5 +21,5 @@ LABEL life.apets.vendor="Halverneus" \
|
|||||||
life.apets.url="https://github.com/halverneus/static-file-server" \
|
life.apets.url="https://github.com/halverneus/static-file-server" \
|
||||||
life.apets.name="Static File Server" \
|
life.apets.name="Static File Server" \
|
||||||
life.apets.description="A tiny static file server" \
|
life.apets.description="A tiny static file server" \
|
||||||
life.apets.version="v1.8.4" \
|
life.apets.version="v1.8.5" \
|
||||||
life.apets.schema-version="1.0"
|
life.apets.schema-version="1.0"
|
||||||
|
11
README.md
11
README.md
@ -1,5 +1,9 @@
|
|||||||
# static-file-server
|
# static-file-server
|
||||||
|
|
||||||
|
<a href="https://github.com/sponsors/halverneus">
|
||||||
|
<img src="img/sponsor.svg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Tiny, simple static file server using environment variables for configuration.
|
Tiny, simple static file server using environment variables for configuration.
|
||||||
@ -47,6 +51,12 @@ URL_PREFIX=
|
|||||||
TLS_CERT=
|
TLS_CERT=
|
||||||
TLS_KEY=
|
TLS_KEY=
|
||||||
|
|
||||||
|
# If TLS certificates are set then the minimum TLS version may also be set. If
|
||||||
|
# the value isn't set then the default minimum TLS version is 1.0. Allowed
|
||||||
|
# values include "TLS10", "TLS11", "TLS12" and "TLS13" for TLS1.0, TLS1.1,
|
||||||
|
# TLS1.2 and TLS1.3, respectively. The value is not case-sensitive.
|
||||||
|
TLS_MIN_VERS=
|
||||||
|
|
||||||
# List of accepted HTTP referrers. Return 403 if HTTP header `Referer` does not
|
# List of accepted HTTP referrers. Return 403 if HTTP header `Referer` does not
|
||||||
# match prefixes provided in the list.
|
# match prefixes provided in the list.
|
||||||
# Examples:
|
# Examples:
|
||||||
@ -73,6 +83,7 @@ referrers: []
|
|||||||
show-listing: true
|
show-listing: true
|
||||||
tls-cert: ""
|
tls-cert: ""
|
||||||
tls-key: ""
|
tls-key: ""
|
||||||
|
tls-min-vers: ""
|
||||||
url-prefix: ""
|
url-prefix: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -75,6 +75,10 @@ ENVIRONMENT VARIABLES
|
|||||||
Path to the TLS key file to serve files using HTTPS. If supplied then
|
Path to the TLS key file to serve files using HTTPS. If supplied then
|
||||||
TLS_CERT must also be supplied. If not supplied, contents will be served
|
TLS_CERT must also be supplied. If not supplied, contents will be served
|
||||||
via HTTPS
|
via HTTPS
|
||||||
|
TLS_MIN_VERS
|
||||||
|
The minimum TLS version to use. If not supplied, defaults to TLS1.0.
|
||||||
|
Acceptable values are 'TLS10', 'TLS11', 'TLS12' and 'TLS13' for TLS1.0,
|
||||||
|
TLS1.1, TLS1.2 and TLS1.3, respectively. Values are not case-sensitive.
|
||||||
URL_PREFIX
|
URL_PREFIX
|
||||||
The prefix to use in the URL path. If supplied, then the prefix must
|
The prefix to use in the URL path. If supplied, then the prefix must
|
||||||
start with a forward-slash and NOT end with a forward-slash. If not
|
start with a forward-slash and NOT end with a forward-slash. If not
|
||||||
@ -98,6 +102,7 @@ CONFIGURATION FILE
|
|||||||
show-listing: true
|
show-listing: true
|
||||||
tls-cert: ""
|
tls-cert: ""
|
||||||
tls-key: ""
|
tls-key: ""
|
||||||
|
tls-min-vers: ""
|
||||||
url-prefix: ""
|
url-prefix: ""
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -150,6 +155,7 @@ USAGE
|
|||||||
export PORT=443
|
export PORT=443
|
||||||
export TLS_CERT=/etc/server/my.machine.crt
|
export TLS_CERT=/etc/server/my.machine.crt
|
||||||
export TLS_KEY=/etc/server/my.machine.key
|
export TLS_KEY=/etc/server/my.machine.key
|
||||||
|
export TLS_MIN_VERS=TLS12
|
||||||
static-file-server
|
static-file-server
|
||||||
Retrieve with: wget https://my.machine/my.file
|
Retrieve with: wget https://my.machine/my.file
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ func listenerSelector() (listener handle.ListenerFunc) {
|
|||||||
// Serve files over HTTP or HTTPS based on paths to TLS files being
|
// Serve files over HTTP or HTTPS based on paths to TLS files being
|
||||||
// provided.
|
// provided.
|
||||||
if 0 < len(config.Get.TLSCert) {
|
if 0 < len(config.Get.TLSCert) {
|
||||||
|
handle.SetMinimumTLSVersion(config.Get.TLSMinVers)
|
||||||
listener = handle.TLSListening(
|
listener = handle.TLSListening(
|
||||||
config.Get.TLSCert,
|
config.Get.TLSCert,
|
||||||
config.Get.TLSKey,
|
config.Get.TLSKey,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -14,16 +16,18 @@ import (
|
|||||||
var (
|
var (
|
||||||
// Get the desired configuration value.
|
// Get the desired configuration value.
|
||||||
Get struct {
|
Get struct {
|
||||||
Cors bool `yaml:"cors"`
|
Cors bool `yaml:"cors"`
|
||||||
Debug bool `yaml:"debug"`
|
Debug bool `yaml:"debug"`
|
||||||
Folder string `yaml:"folder"`
|
Folder string `yaml:"folder"`
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port uint16 `yaml:"port"`
|
Port uint16 `yaml:"port"`
|
||||||
ShowListing bool `yaml:"show-listing"`
|
ShowListing bool `yaml:"show-listing"`
|
||||||
TLSCert string `yaml:"tls-cert"`
|
TLSCert string `yaml:"tls-cert"`
|
||||||
TLSKey string `yaml:"tls-key"`
|
TLSKey string `yaml:"tls-key"`
|
||||||
URLPrefix string `yaml:"url-prefix"`
|
TLSMinVers uint16 `yaml:"-"`
|
||||||
Referrers []string `yaml:"referrers"`
|
TLSMinVersStr string `yaml:"tls-min-vers"`
|
||||||
|
URLPrefix string `yaml:"url-prefix"`
|
||||||
|
Referrers []string `yaml:"referrers"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,6 +41,7 @@ const (
|
|||||||
showListingKey = "SHOW_LISTING"
|
showListingKey = "SHOW_LISTING"
|
||||||
tlsCertKey = "TLS_CERT"
|
tlsCertKey = "TLS_CERT"
|
||||||
tlsKeyKey = "TLS_KEY"
|
tlsKeyKey = "TLS_KEY"
|
||||||
|
tlsMinVersKey = "TLS_MIN_VERS"
|
||||||
urlPrefixKey = "URL_PREFIX"
|
urlPrefixKey = "URL_PREFIX"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,6 +54,7 @@ var (
|
|||||||
defaultShowListing = true
|
defaultShowListing = true
|
||||||
defaultTLSCert = ""
|
defaultTLSCert = ""
|
||||||
defaultTLSKey = ""
|
defaultTLSKey = ""
|
||||||
|
defaultTLSMinVers = ""
|
||||||
defaultURLPrefix = ""
|
defaultURLPrefix = ""
|
||||||
defaultCors = false
|
defaultCors = false
|
||||||
)
|
)
|
||||||
@ -67,6 +73,7 @@ func setDefaults() {
|
|||||||
Get.ShowListing = defaultShowListing
|
Get.ShowListing = defaultShowListing
|
||||||
Get.TLSCert = defaultTLSCert
|
Get.TLSCert = defaultTLSCert
|
||||||
Get.TLSKey = defaultTLSKey
|
Get.TLSKey = defaultTLSKey
|
||||||
|
Get.TLSMinVersStr = defaultTLSMinVers
|
||||||
Get.URLPrefix = defaultURLPrefix
|
Get.URLPrefix = defaultURLPrefix
|
||||||
Get.Cors = defaultCors
|
Get.Cors = defaultCors
|
||||||
}
|
}
|
||||||
@ -74,9 +81,9 @@ func setDefaults() {
|
|||||||
// Load the configuration file.
|
// Load the configuration file.
|
||||||
func Load(filename string) (err error) {
|
func Load(filename string) (err error) {
|
||||||
// If no filename provided, assign envvars.
|
// If no filename provided, assign envvars.
|
||||||
if "" == filename {
|
if filename == "" {
|
||||||
overrideWithEnvVars()
|
overrideWithEnvVars()
|
||||||
return
|
return validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read contents from configuration file.
|
// Read contents from configuration file.
|
||||||
@ -116,6 +123,7 @@ func overrideWithEnvVars() {
|
|||||||
Get.ShowListing = envAsBool(showListingKey, Get.ShowListing)
|
Get.ShowListing = envAsBool(showListingKey, Get.ShowListing)
|
||||||
Get.TLSCert = envAsStr(tlsCertKey, Get.TLSCert)
|
Get.TLSCert = envAsStr(tlsCertKey, Get.TLSCert)
|
||||||
Get.TLSKey = envAsStr(tlsKeyKey, Get.TLSKey)
|
Get.TLSKey = envAsStr(tlsKeyKey, Get.TLSKey)
|
||||||
|
Get.TLSMinVersStr = envAsStr(tlsMinVersKey, Get.TLSMinVersStr)
|
||||||
Get.URLPrefix = envAsStr(urlPrefixKey, Get.URLPrefix)
|
Get.URLPrefix = envAsStr(urlPrefixKey, Get.URLPrefix)
|
||||||
Get.Referrers = envAsStrSlice(referrersKey, Get.Referrers)
|
Get.Referrers = envAsStrSlice(referrersKey, Get.Referrers)
|
||||||
}
|
}
|
||||||
@ -123,8 +131,9 @@ func overrideWithEnvVars() {
|
|||||||
// validate the configuration.
|
// validate the configuration.
|
||||||
func validate() error {
|
func validate() error {
|
||||||
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
// If HTTPS is to be used, verify both TLS_* environment variables are set.
|
||||||
|
useTLS := false
|
||||||
if 0 < len(Get.TLSCert) || 0 < len(Get.TLSKey) {
|
if 0 < len(Get.TLSCert) || 0 < len(Get.TLSKey) {
|
||||||
if 0 == len(Get.TLSCert) || 0 == len(Get.TLSKey) {
|
if len(Get.TLSCert) == 0 || len(Get.TLSKey) == 0 {
|
||||||
msg := "if value for either 'TLS_CERT' or 'TLS_KEY' is set then " +
|
msg := "if value for either 'TLS_CERT' or 'TLS_KEY' is set then " +
|
||||||
"then value for the other must also be set (values are " +
|
"then value for the other must also be set (values are " +
|
||||||
"currently '%s' and '%s', respectively)"
|
"currently '%s' and '%s', respectively)"
|
||||||
@ -132,11 +141,43 @@ func validate() error {
|
|||||||
}
|
}
|
||||||
if _, err := os.Stat(Get.TLSCert); nil != err {
|
if _, err := os.Stat(Get.TLSCert); nil != err {
|
||||||
msg := "value of TLS_CERT is set with filename '%s' that returns %v"
|
msg := "value of TLS_CERT is set with filename '%s' that returns %v"
|
||||||
return fmt.Errorf(msg, err)
|
return fmt.Errorf(msg, Get.TLSCert, err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(Get.TLSKey); nil != err {
|
if _, err := os.Stat(Get.TLSKey); nil != err {
|
||||||
msg := "value of TLS_KEY is set with filename '%s' that returns %v"
|
msg := "value of TLS_KEY is set with filename '%s' that returns %v"
|
||||||
return fmt.Errorf(msg, err)
|
return fmt.Errorf(msg, Get.TLSKey, err)
|
||||||
|
}
|
||||||
|
useTLS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify TLS_MIN_VERS is only (optionally) set if TLS is to be used.
|
||||||
|
Get.TLSMinVers = tls.VersionTLS10
|
||||||
|
if useTLS {
|
||||||
|
if 0 < len(Get.TLSMinVersStr) {
|
||||||
|
var err error
|
||||||
|
if Get.TLSMinVers, err = tlsMinVersAsUint16(
|
||||||
|
Get.TLSMinVersStr,
|
||||||
|
); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For logging minimum TLS version being used while debugging, backfill
|
||||||
|
// the TLSMinVersStr field.
|
||||||
|
switch Get.TLSMinVers {
|
||||||
|
case tls.VersionTLS10:
|
||||||
|
Get.TLSMinVersStr = "TLS1.0"
|
||||||
|
case tls.VersionTLS11:
|
||||||
|
Get.TLSMinVersStr = "TLS1.1"
|
||||||
|
case tls.VersionTLS12:
|
||||||
|
Get.TLSMinVersStr = "TLS1.2"
|
||||||
|
case tls.VersionTLS13:
|
||||||
|
Get.TLSMinVersStr = "TLS1.3"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if 0 < len(Get.TLSMinVersStr) {
|
||||||
|
msg := "value for 'TLS_MIN_VERS' is set but 'TLS_CERT' and 'TLS_KEY' are not"
|
||||||
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +195,7 @@ func validate() error {
|
|||||||
|
|
||||||
// envAsStr returns the value of the environment variable as a string if set.
|
// envAsStr returns the value of the environment variable as a string if set.
|
||||||
func envAsStr(key, fallback string) string {
|
func envAsStr(key, fallback string) string {
|
||||||
if value := os.Getenv(key); "" != value {
|
if value := os.Getenv(key); value != "" {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
@ -163,7 +204,7 @@ func envAsStr(key, fallback string) string {
|
|||||||
// envAsStrSlice returns the value of the environment variable as a slice of
|
// envAsStrSlice returns the value of the environment variable as a slice of
|
||||||
// strings if set.
|
// strings if set.
|
||||||
func envAsStrSlice(key string, fallback []string) []string {
|
func envAsStrSlice(key string, fallback []string) []string {
|
||||||
if value := os.Getenv(key); "" != value {
|
if value := os.Getenv(key); value != "" {
|
||||||
return strings.Split(value, ",")
|
return strings.Split(value, ",")
|
||||||
}
|
}
|
||||||
return fallback
|
return fallback
|
||||||
@ -174,7 +215,7 @@ func envAsUint16(key string, fallback uint16) uint16 {
|
|||||||
// Retrieve the string value of the environment variable. If not set,
|
// Retrieve the string value of the environment variable. If not set,
|
||||||
// fallback is used.
|
// fallback is used.
|
||||||
valueStr := os.Getenv(key)
|
valueStr := os.Getenv(key)
|
||||||
if "" == valueStr {
|
if valueStr == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +239,7 @@ func envAsBool(key string, fallback bool) bool {
|
|||||||
// Retrieve the string value of the environment variable. If not set,
|
// Retrieve the string value of the environment variable. If not set,
|
||||||
// fallback is used.
|
// fallback is used.
|
||||||
valueStr := os.Getenv(key)
|
valueStr := os.Getenv(key)
|
||||||
if "" == valueStr {
|
if valueStr == "" {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,8 +266,26 @@ func strAsBool(value string) (result bool, err error) {
|
|||||||
result = true
|
result = true
|
||||||
default:
|
default:
|
||||||
result = false
|
result = false
|
||||||
msg := "Unknown conversion from string to bool for value '%s'"
|
msg := "unknown conversion from string to bool for value '%s'"
|
||||||
err = fmt.Errorf(msg, value)
|
err = fmt.Errorf(msg, value)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tlsMinVersAsUint16 converts the intent of the passed value into an
|
||||||
|
// enumeration for the crypto/tls package.
|
||||||
|
func tlsMinVersAsUint16(value string) (result uint16, err error) {
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "tls10":
|
||||||
|
result = tls.VersionTLS10
|
||||||
|
case "tls11":
|
||||||
|
result = tls.VersionTLS11
|
||||||
|
case "tls12":
|
||||||
|
result = tls.VersionTLS12
|
||||||
|
case "tls13":
|
||||||
|
result = tls.VersionTLS13
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown value for TLS_MIN_VERS: %s", value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -160,27 +161,35 @@ func TestValidate(t *testing.T) {
|
|||||||
cert string
|
cert string
|
||||||
key string
|
key string
|
||||||
prefix string
|
prefix string
|
||||||
|
minTLS string
|
||||||
isError bool
|
isError bool
|
||||||
}{
|
}{
|
||||||
{"Valid paths w/prefix", validPath, validPath, prefix, false},
|
{"Valid paths w/prefix", validPath, validPath, prefix, "", false},
|
||||||
{"Valid paths wo/prefix", validPath, validPath, empty, false},
|
{"Valid paths wo/prefix", validPath, validPath, empty, "", false},
|
||||||
{"Empty paths w/prefix", empty, empty, prefix, false},
|
{"Empty paths w/prefix", empty, empty, prefix, "", false},
|
||||||
{"Empty paths wo/prefix", empty, empty, empty, false},
|
{"Empty paths wo/prefix", empty, empty, empty, "", false},
|
||||||
{"Mixed paths w/prefix", empty, validPath, prefix, true},
|
{"Mixed paths w/prefix", empty, validPath, prefix, "", true},
|
||||||
{"Alt mixed paths w/prefix", validPath, empty, prefix, true},
|
{"Alt mixed paths w/prefix", validPath, empty, prefix, "", true},
|
||||||
{"Mixed paths wo/prefix", empty, validPath, empty, true},
|
{"Mixed paths wo/prefix", empty, validPath, empty, "", true},
|
||||||
{"Alt mixed paths wo/prefix", validPath, empty, empty, true},
|
{"Alt mixed paths wo/prefix", validPath, empty, empty, "", true},
|
||||||
{"Invalid cert w/prefix", invalidPath, validPath, prefix, true},
|
{"Invalid cert w/prefix", invalidPath, validPath, prefix, "", true},
|
||||||
{"Invalid key w/prefix", validPath, invalidPath, prefix, true},
|
{"Invalid key w/prefix", validPath, invalidPath, prefix, "", true},
|
||||||
{"Invalid cert & key w/prefix", invalidPath, invalidPath, prefix, true},
|
{"Invalid cert & key w/prefix", invalidPath, invalidPath, prefix, "", true},
|
||||||
{"Prefix missing leading /", empty, empty, "my/prefix", true},
|
{"Prefix missing leading /", empty, empty, "my/prefix", "", true},
|
||||||
{"Prefix with trailing /", empty, empty, "/my/prefix/", true},
|
{"Prefix with trailing /", empty, empty, "/my/prefix/", "", true},
|
||||||
|
{"Valid paths w/min ok TLS", validPath, validPath, prefix, "tls11", false},
|
||||||
|
{"Valid paths w/min ok TLS", validPath, validPath, prefix, "tls12", false},
|
||||||
|
{"Valid paths w/min ok TLS", validPath, validPath, prefix, "tls13", false},
|
||||||
|
{"Valid paths w/min bad TLS", validPath, validPath, prefix, "bad", true},
|
||||||
|
{"Empty paths w/min ok TLS", empty, empty, prefix, "tls11", true},
|
||||||
|
{"Empty paths w/min bad TLS", empty, empty, prefix, "bad", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
Get.TLSCert = tc.cert
|
Get.TLSCert = tc.cert
|
||||||
Get.TLSKey = tc.key
|
Get.TLSKey = tc.key
|
||||||
|
Get.TLSMinVersStr = tc.minTLS
|
||||||
Get.URLPrefix = tc.prefix
|
Get.URLPrefix = tc.prefix
|
||||||
err := validate()
|
err := validate()
|
||||||
hasError := nil != err
|
hasError := nil != err
|
||||||
@ -461,3 +470,42 @@ func TestStrAsBool(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTlsMinVersAsUint16(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
result uint16
|
||||||
|
isError bool
|
||||||
|
}{
|
||||||
|
{"Empty value", "", 0, true},
|
||||||
|
{"Valid TLS1.0", "TLS10", tls.VersionTLS10, false},
|
||||||
|
{"Valid TLS1.1", "tls11", tls.VersionTLS11, false},
|
||||||
|
{"Valid TLS1.2", "tls12", tls.VersionTLS12, false},
|
||||||
|
{"Valid TLS1.3", "tLS13", tls.VersionTLS13, false},
|
||||||
|
{"Invalid TLS1.4", "tls14", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := tlsMinVersAsUint16(tc.value)
|
||||||
|
if result != tc.result {
|
||||||
|
t.Errorf(
|
||||||
|
"Expected %d for %s but got %d",
|
||||||
|
tc.result, tc.value, result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if tc.isError && nil == err {
|
||||||
|
t.Errorf(
|
||||||
|
"Expected error for %s but got no error",
|
||||||
|
tc.value,
|
||||||
|
)
|
||||||
|
} else if !tc.isError && nil != err {
|
||||||
|
t.Errorf(
|
||||||
|
"Expected no error for %s but got %v",
|
||||||
|
tc.value, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package handle
|
package handle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -10,14 +11,46 @@ import (
|
|||||||
var (
|
var (
|
||||||
// These assignments are for unit testing.
|
// These assignments are for unit testing.
|
||||||
listenAndServe = http.ListenAndServe
|
listenAndServe = http.ListenAndServe
|
||||||
listenAndServeTLS = http.ListenAndServeTLS
|
listenAndServeTLS = defaultListenAndServeTLS
|
||||||
setHandler = http.HandleFunc
|
setHandler = http.HandleFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
server http.Server
|
// Server options to be set prior to calling the listening function.
|
||||||
|
// minTLSVersion is the minimum allowed TLS version to be used by the
|
||||||
|
// server.
|
||||||
|
minTLSVersion uint16 = tls.VersionTLS10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defaultListenAndServeTLS is the default implementation of the listening
|
||||||
|
// function for serving with TLS enabled. This is, effectively, a copy from
|
||||||
|
// the standard library but with the ability to set the minimum TLS version.
|
||||||
|
func defaultListenAndServeTLS(
|
||||||
|
binding, certFile, keyFile string, handler http.Handler,
|
||||||
|
) error {
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.DefaultServeMux
|
||||||
|
}
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: binding,
|
||||||
|
Handler: handler,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
MinVersion: minTLSVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return server.ListenAndServeTLS(certFile, keyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMinimumTLSVersion to be used by the server.
|
||||||
|
func SetMinimumTLSVersion(version uint16) {
|
||||||
|
if version < tls.VersionTLS10 {
|
||||||
|
version = tls.VersionTLS10
|
||||||
|
} else if version > tls.VersionTLS13 {
|
||||||
|
version = tls.VersionTLS13
|
||||||
|
}
|
||||||
|
minTLSVersion = version
|
||||||
|
}
|
||||||
|
|
||||||
// ListenerFunc accepts the {hostname:port} binding string required by HTTP
|
// ListenerFunc accepts the {hostname:port} binding string required by HTTP
|
||||||
// listeners and the handler (router) function and returns any errors that
|
// listeners and the handler (router) function and returns any errors that
|
||||||
// occur.
|
// occur.
|
||||||
@ -50,7 +83,7 @@ func WithReferrers(serveFile FileServerFunc, referrers []string) FileServerFunc
|
|||||||
func WithLogging(serveFile FileServerFunc) FileServerFunc {
|
func WithLogging(serveFile FileServerFunc) FileServerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, name string) {
|
return func(w http.ResponseWriter, r *http.Request, name string) {
|
||||||
referer := r.Referer()
|
referer := r.Referer()
|
||||||
if 0 == len(referer) {
|
if len(referer) == 0 {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"REQ from '%s': %s %s %s%s -> %s\n",
|
"REQ from '%s': %s %s %s%s -> %s\n",
|
||||||
r.RemoteAddr,
|
r.RemoteAddr,
|
||||||
@ -139,7 +172,7 @@ func TLSListening(tlsCert, tlsKey string) ListenerFunc {
|
|||||||
// passed list of referrers.
|
// passed list of referrers.
|
||||||
func validReferrer(s []string, e string) bool {
|
func validReferrer(s []string, e string) bool {
|
||||||
// Whitelisted referer list is empty. All requests are allowed.
|
// Whitelisted referer list is empty. All requests are allowed.
|
||||||
if 0 == len(s) {
|
if len(s) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package handle
|
package handle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -84,6 +85,28 @@ func teardown() (err error) {
|
|||||||
return os.RemoveAll("tmp")
|
return os.RemoveAll("tmp")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetMinimumTLSVersion(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value uint16
|
||||||
|
expected uint16
|
||||||
|
}{
|
||||||
|
{"Too low", tls.VersionTLS10 - 1, tls.VersionTLS10},
|
||||||
|
{"Lower bounds", tls.VersionTLS10, tls.VersionTLS10},
|
||||||
|
{"Upper bounds", tls.VersionTLS13, tls.VersionTLS13},
|
||||||
|
{"Too high", tls.VersionTLS13 + 1, tls.VersionTLS13},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
SetMinimumTLSVersion(tc.value)
|
||||||
|
if tc.expected != minTLSVersion {
|
||||||
|
t.Errorf("Expected %d but got %d", tc.expected, minTLSVersion)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithReferrers(t *testing.T) {
|
func TestWithReferrers(t *testing.T) {
|
||||||
forbidden := http.StatusForbidden
|
forbidden := http.StatusForbidden
|
||||||
|
|
||||||
|
147
img/sponsor.svg
Normal file
147
img/sponsor.svg
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
aria-hidden="true"
|
||||||
|
height="34.084747"
|
||||||
|
viewBox="0 0 183.50847 34.084747"
|
||||||
|
version="1.1"
|
||||||
|
width="183.50847"
|
||||||
|
style="color:#ff0000"
|
||||||
|
id="svg3763"
|
||||||
|
sodipodi:docname="sponsor.svg"
|
||||||
|
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||||
|
<metadata
|
||||||
|
id="metadata3769">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs3767" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1868"
|
||||||
|
inkscape:window-height="1051"
|
||||||
|
id="namedview3765"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.6875"
|
||||||
|
inkscape:cx="56.941027"
|
||||||
|
inkscape:cy="48.484437"
|
||||||
|
inkscape:window-x="2560"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg3763"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" />
|
||||||
|
<rect
|
||||||
|
id="rect3798"
|
||||||
|
width="182.50847"
|
||||||
|
height="33.084747"
|
||||||
|
x="0.5"
|
||||||
|
y="0.5"
|
||||||
|
rx="10"
|
||||||
|
ry="10"
|
||||||
|
style="fill:#ffeeaa;stroke:#000000;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="color:#ff0000;fill:currentColor;fill-rule:evenodd"
|
||||||
|
d="m 18.682203,10.59322 c -1.336,0 -2.75,1.164 -2.75,3 0,2.15 1.58,4.144 3.365,5.682 a 20.565,20.565 0 0 0 3.135,2.211 20.561,20.561 0 0 0 3.135,-2.211 c 1.785,-1.538 3.365,-3.532 3.365,-5.682 0,-1.836 -1.414,-3 -2.75,-3 -1.373,0 -2.609,0.986 -3.029,2.456 a 0.75,0.75 0 0 1 -1.442,0 c -0.42,-1.47 -1.656,-2.456 -3.029,-2.456 z m 3.75,11.75 -0.345,0.666 -0.002,-10e-4 -0.006,-0.003 -0.018,-0.01 a 7.643,7.643 0 0 1 -0.31,-0.17 22.075,22.075 0 0 1 -3.434,-2.414 c -1.84,-1.587 -3.885,-3.968 -3.885,-6.818 0,-2.664 2.086,-4.5 4.25,-4.5 1.547,0 2.903,0.802 3.75,2.02 0.847,-1.218 2.203,-2.02 3.75,-2.02 2.164,0 4.25,1.836 4.25,4.5 0,2.85 -2.045,5.231 -3.885,6.818 a 22.08,22.08 0 0 1 -3.744,2.584 l -0.018,0.01 -0.006,0.003 h -0.002 z m 0,0 0.345,0.666 a 0.752,0.752 0 0 1 -0.69,0 z"
|
||||||
|
id="path3761"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
aria-label="Buy me a smoothie"
|
||||||
|
transform="translate(66.601695,104.09322)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||||
|
id="flowRoot3790">
|
||||||
|
<path
|
||||||
|
d="m -23.656156,-83.590916 q -0.308,0 -0.674667,-0.01467 -0.352,-0.01467 -0.718667,-0.044 -0.352,-0.02933 -0.704,-0.07333 -0.352,-0.044 -0.645333,-0.117334 v -9.856 q 0.293333,-0.07333 0.645333,-0.117333 0.352,-0.044 0.704,-0.07333 0.366667,-0.02933 0.718667,-0.044 0.352,-0.01467 0.66,-0.01467 0.88,0 1.642667,0.132 0.777333,0.132 1.334667,0.454667 0.572,0.308 0.894666,0.821333 0.322667,0.513333 0.322667,1.261333 0,0.836 -0.396,1.378667 -0.396,0.528 -1.056,0.792 0.894667,0.264 1.422667,0.836 0.528,0.572 0.528,1.613333 0,1.525334 -1.129334,2.302667 -1.114666,0.762667 -3.549333,0.762667 z m -1.334667,-4.825334 v 3.578667 q 0.161333,0.01467 0.381333,0.02933 0.190667,0.01467 0.44,0.02933 0.264,0 0.601334,0 0.630667,0 1.188,-0.07333 0.572,-0.088 0.997333,-0.293333 0.425334,-0.205333 0.674667,-0.572 0.264,-0.366667 0.264,-0.924 0,-0.498667 -0.190667,-0.836 -0.190666,-0.352 -0.557333,-0.557333 -0.352,-0.205334 -0.850667,-0.293334 -0.498666,-0.088 -1.114666,-0.088 z m 0,-1.144 h 1.496 q 0.528,0 0.997334,-0.07333 0.469333,-0.07333 0.806666,-0.264 0.352,-0.190667 0.542667,-0.498667 0.205333,-0.308 0.205333,-0.777333 0,-0.44 -0.205333,-0.733333 -0.205333,-0.308 -0.572,-0.484 -0.352,-0.190667 -0.836,-0.278667 -0.484,-0.088 -1.026667,-0.088 -0.542667,0 -0.850667,0.01467 -0.308,0.01467 -0.557333,0.044 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4607"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m -10.984161,-83.898916 q -0.469333,0.117333 -1.246666,0.249333 -0.762667,0.132 -1.774667,0.132 -0.88,0 -1.481333,-0.249333 -0.601334,-0.264 -0.968,-0.733334 -0.366667,-0.469333 -0.528,-1.1 -0.161334,-0.645333 -0.161334,-1.422666 v -4.282667 h 1.364 v 3.989333 q 0,1.393334 0.44,1.994667 0.44,0.601333 1.481334,0.601333 0.22,0 0.454666,-0.01467 0.234667,-0.01467 0.44,-0.02933 0.205334,-0.02933 0.366667,-0.044 0.176,-0.02933 0.249333,-0.05867 v -6.438667 h 1.364 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4609"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m -9.5321701,-82.25625 q 0.1613333,0.07333 0.4106667,0.132 0.264,0.07333 0.5133333,0.07333 0.8066667,0 1.2613334,-0.366667 0.4546666,-0.352 0.8213333,-1.158667 -0.924,-1.76 -1.7306667,-3.725333 -0.792,-1.98 -1.32,-4.004 h 1.4666667 q 0.1613333,0.66 0.3813333,1.422667 0.2346667,0.762666 0.5133334,1.569333 0.2786666,0.806667 0.6013333,1.613333 0.3226667,0.806667 0.6746667,1.554667 0.5573333,-1.54 0.968,-3.050667 0.4106667,-1.510666 0.7773334,-3.109333 h 1.408 q -0.528,2.156 -1.1733334,4.150667 -0.6453333,1.98 -1.3933333,3.710666 -0.2933334,0.66 -0.616,1.129334 -0.308,0.484 -0.6893334,0.792 -0.3813333,0.308 -0.8653333,0.454666 -0.4693334,0.146667 -1.0706667,0.146667 -0.1613334,0 -0.3373334,-0.02933 -0.176,-0.01467 -0.352,-0.05867 -0.1613333,-0.02933 -0.308,-0.07333 -0.132,-0.044 -0.1906667,-0.07333 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4611"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 2.0398113,-91.085583 q 0.4693334,-0.117333 1.2320001,-0.249333 0.7773333,-0.132 1.7893333,-0.132 0.7333334,0 1.2320001,0.205333 0.4986666,0.190667 0.836,0.572 0.1026666,-0.07333 0.3226666,-0.205333 0.22,-0.132 0.5426667,-0.249334 0.3226667,-0.132 0.7186667,-0.22 0.396,-0.102666 0.8506667,-0.102666 0.8800005,0 1.4373335,0.264 0.557333,0.249333 0.865333,0.718666 0.322667,0.469334 0.425334,1.114667 0.117333,0.645333 0.117333,1.408 v 4.282667 h -1.364 v -3.989334 q 0,-0.674666 -0.07333,-1.158666 -0.05867,-0.484 -0.249334,-0.806667 -0.176,-0.322667 -0.498666,-0.469333 -0.3080005,-0.161334 -0.8066672,-0.161334 -0.6893333,0 -1.144,0.190667 -0.44,0.176 -0.6013334,0.322667 0.1173334,0.381333 0.1760001,0.836 0.058667,0.454666 0.058667,0.953333 v 4.282667 h -1.364 v -3.989334 q 0,-0.674666 -0.073333,-1.158666 -0.073333,-0.484 -0.2640001,-0.806667 -0.176,-0.322667 -0.4986666,-0.469333 -0.308,-0.161334 -0.792,-0.161334 -0.2053334,0 -0.4400001,0.01467 -0.2346666,0.01467 -0.4546666,0.044 -0.2053334,0.01467 -0.3813334,0.044 -0.176,0.02933 -0.2346666,0.044 v 6.438667 H 2.0398113 Z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4613"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 14.242453,-87.477583 q 0,-1.012 0.293334,-1.76 0.293333,-0.762667 0.777333,-1.261333 0.484,-0.498667 1.114667,-0.748 0.630667,-0.249334 1.290667,-0.249334 1.54,0 2.361333,0.968 0.821333,0.953334 0.821333,2.918667 0,0.088 0,0.234667 0,0.132 -0.01467,0.249333 h -5.22133 q 0.088,1.188 0.689333,1.804 0.601334,0.616 1.877334,0.616 0.718667,0 1.202667,-0.117333 0.498666,-0.132 0.748,-0.249334 l 0.190666,1.144 q -0.249333,0.132 -0.88,0.278667 -0.616,0.146667 -1.408,0.146667 -0.997333,0 -1.730667,-0.293334 -0.718666,-0.308 -1.188,-0.836 -0.469333,-0.528 -0.704,-1.246666 -0.22,-0.733334 -0.22,-1.598667 z m 5.236001,-0.748 q 0.01467,-0.924 -0.469334,-1.510667 -0.469333,-0.601333 -1.305333,-0.601333 -0.469333,0 -0.836,0.190667 -0.352,0.176 -0.601334,0.469333 -0.249333,0.293333 -0.396,0.674667 -0.132,0.381333 -0.176,0.777333 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4615"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 28.835744,-84.661583 q 0.484,0 0.850667,-0.01467 0.381333,-0.02933 0.630667,-0.088 v -2.273333 q -0.146667,-0.07333 -0.484,-0.117333 -0.322667,-0.05867 -0.792,-0.05867 -0.308,0 -0.66,0.044 -0.337334,0.044 -0.630667,0.190667 -0.278667,0.132 -0.469333,0.381333 -0.190667,0.234667 -0.190667,0.630667 0,0.733333 0.469333,1.026666 0.469334,0.278667 1.276,0.278667 z m -0.117333,-6.834667 q 0.821333,0 1.378667,0.22 0.572,0.205334 0.909333,0.601334 0.352,0.381333 0.498667,0.924 0.146666,0.528 0.146666,1.173333 v 4.766667 q -0.176,0.02933 -0.498666,0.088 -0.308,0.044 -0.704,0.088 -0.396,0.044 -0.865334,0.07333 -0.454666,0.044 -0.909333,0.044 -0.645333,0 -1.188,-0.132 -0.542667,-0.132 -0.938667,-0.410667 -0.396,-0.293333 -0.616,-0.762666 -0.22,-0.469334 -0.22,-1.129334 0,-0.630666 0.249334,-1.085333 0.264,-0.454667 0.704,-0.733333 0.44,-0.278667 1.026666,-0.410667 0.586667,-0.132 1.232,-0.132 0.205334,0 0.425334,0.02933 0.22,0.01467 0.410666,0.05867 0.205334,0.02933 0.352,0.05867 0.146667,0.02933 0.205334,0.044 v -0.381334 q 0,-0.337333 -0.07333,-0.66 -0.07333,-0.337333 -0.264,-0.586666 -0.190666,-0.264 -0.528,-0.410667 -0.322666,-0.161333 -0.850666,-0.161333 -0.674667,0 -1.188,0.102666 -0.498667,0.088 -0.748,0.190667 l -0.161334,-1.129333 q 0.264,-0.117334 0.88,-0.22 0.616,-0.117334 1.334667,-0.117334 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4617"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 39.073053,-84.661583 q 0.836,0 1.232,-0.22 0.410667,-0.22 0.410667,-0.704 0,-0.498667 -0.396,-0.792 -0.396,-0.293333 -1.305333,-0.66 -0.44,-0.176 -0.850667,-0.352 -0.396,-0.190667 -0.689333,-0.44 -0.293334,-0.249333 -0.469334,-0.601333 -0.176,-0.352 -0.176,-0.865334 0,-1.012 0.748,-1.598666 0.748,-0.601334 2.038667,-0.601334 0.322667,0 0.645333,0.044 0.322667,0.02933 0.601334,0.088 0.278667,0.044 0.484,0.102667 0.22,0.05867 0.337333,0.102667 l -0.249333,1.173333 q -0.22,-0.117333 -0.689333,-0.234667 -0.469334,-0.132 -1.129334,-0.132 -0.572,0 -0.997333,0.234667 -0.425334,0.22 -0.425334,0.704 0,0.249333 0.088,0.44 0.102667,0.190667 0.293334,0.352 0.205333,0.146667 0.498666,0.278667 0.293334,0.132 0.704,0.278666 0.542667,0.205334 0.968001,0.410667 0.425333,0.190667 0.718666,0.454667 0.308,0.264 0.469334,0.645333 0.161333,0.366667 0.161333,0.909333 0,1.056 -0.792,1.598667 -0.777333,0.542667 -2.229334,0.542667 -1.012,0 -1.584,-0.176 -0.572,-0.161334 -0.777333,-0.249334 l 0.249333,-1.173333 q 0.234667,0.088 0.748,0.264 0.513334,0.176 1.364,0.176 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4619"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 43.839708,-91.085583 q 0.469333,-0.117333 1.232,-0.249333 0.777333,-0.132 1.789333,-0.132 0.733333,0 1.232,0.205333 0.498667,0.190667 0.836,0.572 0.102667,-0.07333 0.322667,-0.205333 0.22,-0.132 0.542666,-0.249334 0.322667,-0.132 0.718667,-0.22 0.396,-0.102666 0.850667,-0.102666 0.88,0 1.437333,0.264 0.557333,0.249333 0.865333,0.718666 0.322667,0.469334 0.425334,1.114667 0.117333,0.645333 0.117333,1.408 v 4.282667 h -1.364 v -3.989334 q 0,-0.674666 -0.07333,-1.158666 -0.05867,-0.484 -0.249334,-0.806667 -0.176,-0.322667 -0.498666,-0.469333 -0.308,-0.161334 -0.806667,-0.161334 -0.689333,0 -1.144,0.190667 -0.44,0.176 -0.601333,0.322667 0.117333,0.381333 0.176,0.836 0.05867,0.454666 0.05867,0.953333 v 4.282667 h -1.364 v -3.989334 q 0,-0.674666 -0.07333,-1.158666 -0.07333,-0.484 -0.264,-0.806667 -0.176,-0.322667 -0.498667,-0.469333 -0.308,-0.161334 -0.792,-0.161334 -0.205333,0 -0.44,0.01467 -0.234666,0.01467 -0.454666,0.044 -0.205334,0.01467 -0.381334,0.044 -0.176,0.02933 -0.234666,0.044 v 6.438667 h -1.364 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4621"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 63.199683,-87.49225 q 0,0.909334 -0.264,1.642667 -0.264,0.733333 -0.748,1.261333 -0.469333,0.528 -1.129333,0.821334 -0.66,0.278666 -1.437334,0.278666 -0.777333,0 -1.437333,-0.278666 -0.66,-0.293334 -1.144,-0.821334 -0.469333,-0.528 -0.733333,-1.261333 -0.264,-0.733333 -0.264,-1.642667 0,-0.894666 0.264,-1.628 0.264,-0.748 0.733333,-1.276 0.484,-0.528 1.144,-0.806666 0.66,-0.293334 1.437333,-0.293334 0.777334,0 1.437334,0.293334 0.66,0.278666 1.129333,0.806666 0.484,0.528 0.748,1.276 0.264,0.733334 0.264,1.628 z m -1.422667,0 q 0,-1.290666 -0.586666,-2.038666 -0.572,-0.762667 -1.569334,-0.762667 -0.997333,0 -1.584,0.762667 -0.572,0.748 -0.572,2.038666 0,1.290667 0.572,2.053334 0.586667,0.748 1.584,0.748 0.997334,0 1.569334,-0.748 0.586666,-0.762667 0.586666,-2.053334 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4623"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 71.852988,-87.49225 q 0,0.909334 -0.264,1.642667 -0.264,0.733333 -0.748,1.261333 -0.469333,0.528 -1.129333,0.821334 -0.66,0.278666 -1.437334,0.278666 -0.777333,0 -1.437333,-0.278666 -0.66,-0.293334 -1.144,-0.821334 -0.469333,-0.528 -0.733333,-1.261333 -0.264,-0.733333 -0.264,-1.642667 0,-0.894666 0.264,-1.628 0.264,-0.748 0.733333,-1.276 0.484,-0.528 1.144,-0.806666 0.66,-0.293334 1.437333,-0.293334 0.777334,0 1.437334,0.293334 0.66,0.278666 1.129333,0.806666 0.484,0.528 0.748,1.276 0.264,0.733334 0.264,1.628 z m -1.422666,0 q 0,-1.290666 -0.586667,-2.038666 -0.572,-0.762667 -1.569334,-0.762667 -0.997333,0 -1.584,0.762667 -0.572,0.748 -0.572,2.038666 0,1.290667 0.572,2.053334 0.586667,0.748 1.584,0.748 0.997334,0 1.569334,-0.748 0.586667,-0.762667 0.586667,-2.053334 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4625"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 75.050293,-91.305583 h 2.889334 v 1.144 h -2.889334 v 3.52 q 0,0.572 0.088,0.953333 0.088,0.366667 0.264,0.586667 0.176,0.205333 0.44,0.293333 0.264,0.088 0.616,0.088 0.616,0 0.982667,-0.132 0.381333,-0.146666 0.528,-0.205333 l 0.264,1.129333 q -0.205333,0.102667 -0.718667,0.249334 -0.513333,0.161333 -1.173333,0.161333 -0.777333,0 -1.290667,-0.190667 -0.498667,-0.205333 -0.806667,-0.601333 -0.308,-0.396 -0.44,-0.968 -0.117333,-0.586667 -0.117333,-1.349333 v -6.805334 l 1.364,-0.234666 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4627"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 79.670274,-83.678916 v -11.146667 l 1.364,-0.234667 v 3.901334 q 0.381333,-0.146667 0.806667,-0.22 0.44,-0.088 0.865333,-0.088 0.909333,0 1.510667,0.264 0.601333,0.249333 0.953333,0.718666 0.366667,0.454667 0.513333,1.1 0.146667,0.645334 0.146667,1.422667 v 4.282667 h -1.364 v -3.989334 q 0,-0.704 -0.102667,-1.202666 -0.088,-0.498667 -0.308,-0.806667 -0.22,-0.308 -0.586666,-0.44 -0.366667,-0.146667 -0.909334,-0.146667 -0.22,0 -0.454666,0.02933 -0.234667,0.02933 -0.454667,0.07333 -0.205333,0.02933 -0.381333,0.07333 -0.161334,0.044 -0.234667,0.07333 v 6.336 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4629"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 89.408931,-83.678916 h -1.364 v -7.626667 h 1.364 z m -0.689334,-9.005334 q -0.366666,0 -0.630666,-0.234666 -0.249334,-0.249334 -0.249334,-0.66 0,-0.410667 0.249334,-0.645334 0.264,-0.249333 0.630666,-0.249333 0.366667,0 0.616,0.249333 0.264,0.234667 0.264,0.645334 0,0.410666 -0.264,0.66 -0.249333,0.234666 -0.616,0.234666 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4631"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 91.330237,-87.477583 q 0,-1.012 0.293334,-1.76 0.293333,-0.762667 0.777333,-1.261333 0.484,-0.498667 1.114667,-0.748 0.630666,-0.249334 1.290666,-0.249334 1.54,0 2.361334,0.968 0.821333,0.953334 0.821333,2.918667 0,0.088 0,0.234667 0,0.132 -0.01467,0.249333 h -5.221333 q 0.088,1.188 0.689333,1.804 0.601334,0.616 1.877334,0.616 0.718666,0 1.202666,-0.117333 0.498667,-0.132 0.748,-0.249334 l 0.190667,1.144 q -0.249333,0.132 -0.88,0.278667 -0.616,0.146667 -1.408,0.146667 -0.997333,0 -1.730667,-0.293334 -0.718666,-0.308 -1.188,-0.836 -0.469333,-0.528 -0.704,-1.246666 -0.22,-0.733334 -0.22,-1.598667 z m 5.236,-0.748 q 0.01467,-0.924 -0.469333,-1.510667 -0.469333,-0.601333 -1.305333,-0.601333 -0.469334,0 -0.836,0.190667 -0.352,0.176 -0.601334,0.469333 -0.249333,0.293333 -0.396,0.674667 -0.132,0.381333 -0.176,0.777333 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.66666698px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke:none;stroke-opacity:1"
|
||||||
|
id="path4633"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 21 KiB |
Loading…
Reference in New Issue
Block a user