Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
id
auth
Commits
c977307b
Commit
c977307b
authored
Nov 07, 2020
by
ale
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add vendored dependencies
parent
d95488b4
Pipeline
#8376
passed with stages
in 1 minute and 35 seconds
Changes
19
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1964 additions
and
0 deletions
+1964
-0
vendor/git.autistici.org/ai3/go-common/serverutil/http.go
vendor/git.autistici.org/ai3/go-common/serverutil/http.go
+228
-0
vendor/git.autistici.org/ai3/go-common/serverutil/json.go
vendor/git.autistici.org/ai3/go-common/serverutil/json.go
+47
-0
vendor/git.autistici.org/ai3/go-common/serverutil/load_shedding.go
...t.autistici.org/ai3/go-common/serverutil/load_shedding.go
+51
-0
vendor/git.autistici.org/ai3/go-common/serverutil/proxy_headers.go
...t.autistici.org/ai3/go-common/serverutil/proxy_headers.go
+78
-0
vendor/git.autistici.org/ai3/go-common/serverutil/tls.go
vendor/git.autistici.org/ai3/go-common/serverutil/tls.go
+165
-0
vendor/github.com/gorilla/handlers/LICENSE
vendor/github.com/gorilla/handlers/LICENSE
+22
-0
vendor/github.com/gorilla/handlers/README.md
vendor/github.com/gorilla/handlers/README.md
+55
-0
vendor/github.com/gorilla/handlers/canonical.go
vendor/github.com/gorilla/handlers/canonical.go
+74
-0
vendor/github.com/gorilla/handlers/compress.go
vendor/github.com/gorilla/handlers/compress.go
+148
-0
vendor/github.com/gorilla/handlers/cors.go
vendor/github.com/gorilla/handlers/cors.go
+338
-0
vendor/github.com/gorilla/handlers/doc.go
vendor/github.com/gorilla/handlers/doc.go
+9
-0
vendor/github.com/gorilla/handlers/handlers.go
vendor/github.com/gorilla/handlers/handlers.go
+174
-0
vendor/github.com/gorilla/handlers/handlers_go18.go
vendor/github.com/gorilla/handlers/handlers_go18.go
+21
-0
vendor/github.com/gorilla/handlers/handlers_pre18.go
vendor/github.com/gorilla/handlers/handlers_pre18.go
+7
-0
vendor/github.com/gorilla/handlers/logging.go
vendor/github.com/gorilla/handlers/logging.go
+252
-0
vendor/github.com/gorilla/handlers/proxy_headers.go
vendor/github.com/gorilla/handlers/proxy_headers.go
+120
-0
vendor/github.com/gorilla/handlers/recovery.go
vendor/github.com/gorilla/handlers/recovery.go
+91
-0
vendor/golang.org/x/sync/errgroup/errgroup.go
vendor/golang.org/x/sync/errgroup/errgroup.go
+66
-0
vendor/vendor.json
vendor/vendor.json
+18
-0
No files found.
vendor/git.autistici.org/ai3/go-common/serverutil/http.go
0 → 100644
View file @
c977307b
package
serverutil
import
(
"context"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"net/http"
_
"net/http/pprof"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"git.autistici.org/ai3/go-common/tracing"
"github.com/coreos/go-systemd/daemon"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var
gracefulShutdownTimeout
=
3
*
time
.
Second
// ServerConfig stores common HTTP/HTTPS server configuration parameters.
type
ServerConfig
struct
{
TLS
*
TLSServerConfig
`yaml:"tls"`
MaxInflightRequests
int
`yaml:"max_inflight_requests"`
RequestTimeoutSecs
int
`yaml:"request_timeout"`
TrustedForwarders
[]
string
`yaml:"trusted_forwarders"`
}
func
(
config
*
ServerConfig
)
buildHTTPServer
(
h
http
.
Handler
)
(
*
http
.
Server
,
error
)
{
var
tlsConfig
*
tls
.
Config
var
err
error
if
config
!=
nil
{
if
config
.
TLS
!=
nil
{
tlsConfig
,
err
=
config
.
TLS
.
TLSConfig
()
if
err
!=
nil
{
return
nil
,
err
}
h
,
err
=
config
.
TLS
.
TLSAuthWrapper
(
h
)
if
err
!=
nil
{
return
nil
,
err
}
}
// If TrustedForwarders is defined, rewrite the request
// headers using X-Forwarded-Proto and X-Real-IP.
if
len
(
config
.
TrustedForwarders
)
>
0
{
h
,
err
=
newProxyHeaders
(
h
,
config
.
TrustedForwarders
)
if
err
!=
nil
{
return
nil
,
err
}
}
// If MaxInflightRequests is set, enable the load
// shedding wrapper.
if
config
.
MaxInflightRequests
>
0
{
h
=
newLoadSheddingWrapper
(
config
.
MaxInflightRequests
,
h
)
}
// Wrap the handler with a TimeoutHandler if 'request_timeout'
// is set.
if
config
.
RequestTimeoutSecs
>
0
{
h
=
http
.
TimeoutHandler
(
h
,
time
.
Duration
(
config
.
RequestTimeoutSecs
)
*
time
.
Second
,
""
)
}
}
// These are not meant to be external-facing servers, so we
// can be generous with the timeouts to keep the number of
// reconnections low.
return
&
http
.
Server
{
Handler
:
addDefaultHandlers
(
h
),
ReadTimeout
:
30
*
time
.
Second
,
WriteTimeout
:
30
*
time
.
Second
,
IdleTimeout
:
600
*
time
.
Second
,
TLSConfig
:
tlsConfig
,
},
nil
}
// Serve HTTP(S) content on the specified address. If config.TLS is
// not nil, enable HTTPS and TLS authentication.
//
// This function will return an error if there are problems creating
// the listener, otherwise it will handle graceful termination on
// SIGINT or SIGTERM and return nil.
func
Serve
(
h
http
.
Handler
,
config
*
ServerConfig
,
addr
string
)
error
{
// Wrap with tracing handler (exclude metrics and other
// debugging endpoints).
h
=
tracing
.
WrapHandler
(
h
,
guessEndpointName
(
addr
))
// Create the HTTP server.
srv
,
err
:=
config
.
buildHTTPServer
(
h
)
if
err
!=
nil
{
return
err
}
// Create the net.Listener first, so we can detect
// initialization-time errors safely.
l
,
err
:=
net
.
Listen
(
"tcp"
,
addr
)
if
err
!=
nil
{
return
err
}
if
srv
.
TLSConfig
!=
nil
{
l
=
tls
.
NewListener
(
l
,
srv
.
TLSConfig
)
}
// Install a signal handler for gentle process termination.
done
:=
make
(
chan
struct
{})
sigCh
:=
make
(
chan
os
.
Signal
,
1
)
go
func
()
{
<-
sigCh
log
.
Printf
(
"exiting"
)
// Gracefully terminate for 3 seconds max, then shut
// down remaining clients.
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
gracefulShutdownTimeout
)
defer
cancel
()
if
err
=
srv
.
Shutdown
(
ctx
);
err
==
context
.
Canceled
{
if
err
=
srv
.
Close
();
err
!=
nil
{
log
.
Printf
(
"error terminating server: %v"
,
err
)
}
}
close
(
done
)
}()
signal
.
Notify
(
sigCh
,
syscall
.
SIGINT
,
syscall
.
SIGTERM
)
// Notify systemd that we are ready to serve. This call is
// allowed to fail (in case there is no systemd).
daemon
.
SdNotify
(
false
,
"READY=1"
)
// nolint
err
=
srv
.
Serve
(
l
)
if
err
!=
http
.
ErrServerClosed
{
return
err
}
<-
done
return
nil
}
func
addDefaultHandlers
(
h
http
.
Handler
)
http
.
Handler
{
root
:=
http
.
NewServeMux
()
// Add an endpoint for HTTP health checking probes.
root
.
Handle
(
"/health"
,
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
io
.
WriteString
(
w
,
"OK"
)
// nolint
}))
// Add an endpoint to serve Prometheus metrics.
root
.
Handle
(
"/metrics"
,
promhttp
.
Handler
())
// Let the default net/http handler deal with /debug/
// URLs. Packages such as net/http/pprof register their
// handlers there in ways that aren't reproducible.
root
.
Handle
(
"/debug/"
,
http
.
DefaultServeMux
)
// Forward everything else to the main handler, adding
// Prometheus instrumentation (requests to /metrics and
// /health are not included).
root
.
Handle
(
"/"
,
promhttp
.
InstrumentHandlerInFlight
(
inFlightRequests
,
promhttp
.
InstrumentHandlerCounter
(
totalRequests
,
propagateDeadline
(
h
))))
return
root
}
const
deadlineHeader
=
"X-RPC-Deadline"
// Read an eventual deadline from the HTTP request, and set it as the
// deadline of the request context.
func
propagateDeadline
(
h
http
.
Handler
)
http
.
Handler
{
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
if
hdr
:=
req
.
Header
.
Get
(
deadlineHeader
);
hdr
!=
""
{
if
deadlineNano
,
err
:=
strconv
.
ParseInt
(
hdr
,
10
,
64
);
err
==
nil
{
deadline
:=
time
.
Unix
(
0
,
deadlineNano
)
ctx
,
cancel
:=
context
.
WithDeadline
(
req
.
Context
(),
deadline
)
defer
cancel
()
req
=
req
.
WithContext
(
ctx
)
}
}
h
.
ServeHTTP
(
w
,
req
)
})
}
func
guessEndpointName
(
addr
string
)
string
{
_
,
port
,
err
:=
net
.
SplitHostPort
(
addr
)
if
err
!=
nil
{
return
addr
}
host
,
err
:=
os
.
Hostname
()
if
err
!=
nil
{
return
addr
}
return
fmt
.
Sprintf
(
"%s:%s"
,
host
,
port
)
}
// HTTP-related metrics.
var
(
// Since we instrument the root HTTP handler, we don't really
// have a good way to set the 'handler' label based on the
// request URL - but still, we'd like to set the label to
// match what the other Prometheus jobs do. So we just set it
// to 'all'.
totalRequests
=
prometheus
.
NewCounterVec
(
prometheus
.
CounterOpts
{
Name
:
"http_requests_total"
,
Help
:
"Total number of requests."
,
ConstLabels
:
prometheus
.
Labels
{
"handler"
:
"all"
,
},
},
[]
string
{
"code"
,
"method"
},
)
inFlightRequests
=
prometheus
.
NewGauge
(
prometheus
.
GaugeOpts
{
Name
:
"http_requests_inflight"
,
Help
:
"Number of in-flight requests."
,
},
)
)
func
init
()
{
prometheus
.
MustRegister
(
totalRequests
,
inFlightRequests
)
}
vendor/git.autistici.org/ai3/go-common/serverutil/json.go
0 → 100644
View file @
c977307b
package
serverutil
import
(
"encoding/json"
"log"
"net/http"
)
// DecodeJSONRequest decodes a JSON object from an incoming HTTP POST
// request and return true when successful. In case of errors, it will
// write an error response to w and return false.
func
DecodeJSONRequest
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
obj
interface
{})
bool
{
if
r
.
Method
!=
"POST"
{
http
.
Error
(
w
,
"Method not allowed"
,
http
.
StatusMethodNotAllowed
)
return
false
}
if
r
.
Header
.
Get
(
"Content-Type"
)
!=
"application/json"
{
http
.
Error
(
w
,
"Need JSON request"
,
http
.
StatusBadRequest
)
return
false
}
if
err
:=
json
.
NewDecoder
(
r
.
Body
)
.
Decode
(
obj
);
err
!=
nil
{
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusBadRequest
)
return
false
}
return
true
}
// EncodeJSONResponse writes an application/json response to w.
func
EncodeJSONResponse
(
w
http
.
ResponseWriter
,
obj
interface
{})
{
data
,
err
:=
json
.
Marshal
(
obj
)
if
err
!=
nil
{
log
.
Printf
(
"JSON serialization error: %v"
,
err
)
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
w
.
Header
()
.
Set
(
"Content-Type"
,
"application/json"
)
w
.
Header
()
.
Set
(
"Pragma"
,
"no-cache"
)
w
.
Header
()
.
Set
(
"Cache-Control"
,
"no-store"
)
w
.
Header
()
.
Set
(
"Expires"
,
"-1"
)
w
.
Header
()
.
Set
(
"X-Content-Type-Options"
,
"nosniff"
)
if
_
,
err
=
w
.
Write
(
data
);
err
!=
nil
{
log
.
Printf
(
"error writing response: %v"
,
err
)
}
}
vendor/git.autistici.org/ai3/go-common/serverutil/load_shedding.go
0 → 100644
View file @
c977307b
package
serverutil
import
(
"net/http"
"sync/atomic"
"github.com/prometheus/client_golang/prometheus"
)
type
loadSheddingWrapper
struct
{
limit
,
inflight
int32
h
http
.
Handler
}
func
newLoadSheddingWrapper
(
limit
int
,
h
http
.
Handler
)
*
loadSheddingWrapper
{
return
&
loadSheddingWrapper
{
limit
:
int32
(
limit
),
h
:
h
}
}
func
(
l
*
loadSheddingWrapper
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
inflight
:=
atomic
.
AddInt32
(
&
l
.
inflight
,
1
)
defer
atomic
.
AddInt32
(
&
l
.
inflight
,
-
1
)
if
inflight
>
l
.
limit
{
throttledRequests
.
Inc
()
w
.
Header
()
.
Set
(
"Connection"
,
"close"
)
http
.
Error
(
w
,
"Throttled"
,
http
.
StatusTooManyRequests
)
return
}
allowedRequests
.
Inc
()
l
.
h
.
ServeHTTP
(
w
,
r
)
}
var
(
throttledRequests
=
prometheus
.
NewCounter
(
prometheus
.
CounterOpts
{
Name
:
"ls_throttled_requests"
,
Help
:
"Requests throttled by the load shedding wrapper."
,
},
)
allowedRequests
=
prometheus
.
NewCounter
(
prometheus
.
CounterOpts
{
Name
:
"ls_allowed_requests"
,
Help
:
"Requests allowed by the load shedding wrapper."
,
},
)
)
func
init
()
{
prometheus
.
MustRegister
(
throttledRequests
,
allowedRequests
)
}
vendor/git.autistici.org/ai3/go-common/serverutil/proxy_headers.go
0 → 100644
View file @
c977307b
package
serverutil
import
(
"fmt"
"net"
"net/http"
"github.com/gorilla/handlers"
)
type
proxyHeaders
struct
{
wrap
,
phWrap
http
.
Handler
forwarders
[]
net
.
IPNet
}
func
newProxyHeaders
(
h
http
.
Handler
,
trustedForwarders
[]
string
)
(
http
.
Handler
,
error
)
{
f
,
err
:=
parseIPNetList
(
trustedForwarders
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
proxyHeaders
{
wrap
:
h
,
phWrap
:
handlers
.
ProxyHeaders
(
h
),
forwarders
:
f
,
},
nil
}
func
(
p
*
proxyHeaders
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
host
,
_
,
err
:=
net
.
SplitHostPort
(
r
.
RemoteAddr
)
if
err
!=
nil
{
host
=
r
.
RemoteAddr
}
ip
:=
net
.
ParseIP
(
host
)
if
ip
!=
nil
&&
matchIPNetList
(
ip
,
p
.
forwarders
)
{
p
.
phWrap
.
ServeHTTP
(
w
,
r
)
return
}
p
.
wrap
.
ServeHTTP
(
w
,
r
)
}
func
fullMask
(
ip
net
.
IP
)
net
.
IPMask
{
if
ip
.
To4
()
==
nil
{
return
net
.
CIDRMask
(
128
,
128
)
}
return
net
.
CIDRMask
(
32
,
32
)
}
// ParseIPNetList turns a comma-separated list of IP addresses or CIDR
// networks into a net.IPNet slice.
func
parseIPNetList
(
iplist
[]
string
)
([]
net
.
IPNet
,
error
)
{
var
nets
[]
net
.
IPNet
for
_
,
s
:=
range
iplist
{
if
s
==
""
{
continue
}
_
,
ipnet
,
err
:=
net
.
ParseCIDR
(
s
)
if
err
!=
nil
{
ip
:=
net
.
ParseIP
(
s
)
if
ip
==
nil
{
return
nil
,
fmt
.
Errorf
(
"could not parse '%s'"
,
s
)
}
ipnet
=
&
net
.
IPNet
{
IP
:
ip
,
Mask
:
fullMask
(
ip
)}
}
nets
=
append
(
nets
,
*
ipnet
)
}
return
nets
,
nil
}
// MatchIPNetList returns true if the given IP address matches one of
// the specified networks.
func
matchIPNetList
(
ip
net
.
IP
,
nets
[]
net
.
IPNet
)
bool
{
for
_
,
n
:=
range
nets
{
if
n
.
Contains
(
ip
)
{
return
true
}
}
return
false
}
vendor/git.autistici.org/ai3/go-common/serverutil/tls.go
0 → 100644
View file @
c977307b
package
serverutil
import
(
"crypto/tls"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"strings"
common
"git.autistici.org/ai3/go-common"
)
// TLSAuthACL describes a single access control entry. Path and
// CommonName are anchored regular expressions (they must match the
// entire string).
type
TLSAuthACL
struct
{
Path
string
`yaml:"path"`
CommonName
string
`yaml:"cn"`
pathRx
,
cnRx
*
regexp
.
Regexp
}
func
(
p
*
TLSAuthACL
)
compile
()
error
{
var
err
error
p
.
pathRx
,
err
=
regexp
.
Compile
(
"^"
+
p
.
Path
+
"$"
)
if
err
!=
nil
{
return
err
}
p
.
cnRx
,
err
=
regexp
.
Compile
(
"^"
+
p
.
CommonName
+
"$"
)
return
err
}
func
(
p
*
TLSAuthACL
)
match
(
req
*
http
.
Request
)
bool
{
if
!
p
.
pathRx
.
MatchString
(
req
.
URL
.
Path
)
{
return
false
}
for
_
,
cert
:=
range
req
.
TLS
.
PeerCertificates
{
if
p
.
cnRx
.
MatchString
(
cert
.
Subject
.
CommonName
)
{
return
true
}
}
return
false
}
// TLSAuthACLListFlag is a convenience type that allows callers to use
// the 'flag' package to specify a list of TLSAuthACL objects. It
// implements the flag.Value interface.
type
TLSAuthACLListFlag
[]
*
TLSAuthACL
func
(
l
TLSAuthACLListFlag
)
String
()
string
{
var
out
[]
string
for
_
,
acl
:=
range
l
{
out
=
append
(
out
,
fmt
.
Sprintf
(
"%s:%s"
,
acl
.
Path
,
acl
.
CommonName
))
}
return
strings
.
Join
(
out
,
","
)
}
func
(
l
*
TLSAuthACLListFlag
)
Set
(
value
string
)
error
{
parts
:=
strings
.
SplitN
(
value
,
":"
,
2
)
if
len
(
parts
)
!=
2
{
return
errors
.
New
(
"bad acl format"
)
}
*
l
=
append
(
*
l
,
&
TLSAuthACL
{
Path
:
parts
[
0
],
CommonName
:
parts
[
1
],
})
return
nil
}
// TLSAuthConfig stores access control lists for TLS authentication. Access
// control lists are matched against the request path and the
// CommonName component of the peer certificate subject.
type
TLSAuthConfig
struct
{
Allow
[]
*
TLSAuthACL
`yaml:"allow"`
}
func
(
c
*
TLSAuthConfig
)
match
(
req
*
http
.
Request
)
bool
{
// Fail *OPEN* if unconfigured.
if
c
==
nil
||
len
(
c
.
Allow
)
==
0
{
return
true
}
for
_
,
acl
:=
range
c
.
Allow
{
if
acl
.
match
(
req
)
{
return
true
}
}
return
false
}
var
serverCiphers
=
[]
uint16
{
tls
.
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
,
tls
.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
,
}
// TLSServerConfig configures a TLS server with client authentication
// and authorization based on the client X509 certificate.
type
TLSServerConfig
struct
{
Cert
string
`yaml:"cert"`
Key
string
`yaml:"key"`
CA
string
`yaml:"ca"`
Auth
*
TLSAuthConfig
`yaml:"acl"`
}
// TLSConfig returns a tls.Config created with the current configuration.
func
(
c
*
TLSServerConfig
)
TLSConfig
()
(
*
tls
.
Config
,
error
)
{
cert
,
err
:=
tls
.
LoadX509KeyPair
(
c
.
Cert
,
c
.
Key
)
if
err
!=
nil
{
return
nil
,
err
}
// Set some TLS-level parameters (cipher-related), assuming
// we're using EC keys.
tlsConf
:=
&
tls
.
Config
{
Certificates
:
[]
tls
.
Certificate
{
cert
},
CipherSuites
:
serverCiphers
,
MinVersion
:
tls
.
VersionTLS12
,
PreferServerCipherSuites
:
true
,
}
// Require client certificates if a CA is specified.
if
c
.
CA
!=
""
{
cas
,
err
:=
common
.
LoadCA
(
c
.
CA
)
if
err
!=
nil
{
return
nil
,
err
}
tlsConf
.
ClientAuth
=
tls
.
RequireAndVerifyClientCert
tlsConf
.
ClientCAs
=
cas
}
tlsConf
.
BuildNameToCertificate
()
return
tlsConf
,
nil
}
// TLSAuthWrapper protects a root HTTP handler with TLS authentication.
func
(
c
*
TLSServerConfig
)
TLSAuthWrapper
(
h
http
.
Handler
)
(
http
.
Handler
,
error
)
{
// Compile regexps.
if
c
.
Auth
!=
nil
{
for
_
,
acl
:=
range
c
.
Auth
.
Allow
{
if
err
:=
acl
.
compile
();
err
!=
nil
{
return
nil
,
err
}
}
}
// Build the wrapper function to check client certificates
// identities (looking at the CN part of the X509 subject).
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
if
c
.
Auth
.
match
(
r
)
{
h
.
ServeHTTP
(
w
,
r
)
return
}
// Log the failed access, useful for debugging.
var
tlsmsg
string
if
r
.
TLS
!=
nil
&&
len
(
r
.
TLS
.
PeerCertificates
)
>
0
{
tlsmsg
=
fmt
.
Sprintf
(
" TLS client '%s' at"
,
r
.
TLS
.
PeerCertificates
[
0
]
.
Subject
.
CommonName
)
}
log
.
Printf
(
"unauthorized access to %s from %s%s"
,
r
.
URL
.
Path
,
tlsmsg
,
r
.
RemoteAddr
)
http
.
Error
(
w
,
"Forbidden"
,
http
.
StatusForbidden
)
}),
nil
}
vendor/github.com/gorilla/handlers/LICENSE
0 → 100644
View file @
c977307b
Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPO