Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"fmt"
"net/http"
)
// WriteErrorPage create a formatted error page response containing the provided
// information and writes it to the provided http.ResponseWriter.
func WriteErrorPage(rw http.ResponseWriter, code int, title string, message string) {
if title == "" {
title = http.StatusText(code)
}
text := fmt.Sprintf("%d %s", code, title)
if message != "" {
text = text + " - " + message
}
http.Error(rw, text, code)
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"fmt"
)
// ErrorWithDescription is an interface binding the standard error
// inteface with a description.
type ErrorWithDescription interface {
error
Description() string
}
// DescribeError returns a wrapped version for errors which contain additional
// fields. The wrapped version contains all fields as a string value. Use this
// for general purpose logging of rich errors.
func DescribeError(err error) error {
switch err.(type) {
case ErrorWithDescription:
err = fmt.Errorf("%s - %s", err, err.(ErrorWithDescription).Description())
}
return err
}
// ErrorAsFields returns a mapping of all fields of the provided error.
func ErrorAsFields(err error) map[string]interface{} {
if err == nil {
return nil
}
fields := make(map[string]interface{})
fields["error"] = err.Error()
switch err.(type) {
case ErrorWithDescription:
fields["desc"] = err.(ErrorWithDescription).Description()
}
return fields
}
+96
View File
@@ -0,0 +1,96 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"crypto/tls"
"net"
"net/http"
"time"
"golang.org/x/net/http2"
"github.com/libregraph/lico/version"
)
const (
defaultHTTPTimeout = 30 * time.Second
defaultHTTPKeepAlive = 30 * time.Second
defaultHTTPMaxIdleConns = 100
defaultHTTPIdleConnTimeout = 90 * time.Second
defaultHTTPTLSHandshakeTimeout = 10 * time.Second
defaultHTTPExpectContinueTimeout = 1 * time.Second
)
// DefaultHTTPUserAgent is the User-Agent Header which should be used when
// making HTTP requests.
var DefaultHTTPUserAgent = "LibreGraph-Connect/" + version.Version
// HTTPTransportWithTLSClientConfig creates a new http.Transport with sane
// default settings using the provided tls.Config.
func HTTPTransportWithTLSClientConfig(tlsClientConfig *tls.Config) *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: defaultHTTPTimeout,
KeepAlive: defaultHTTPKeepAlive,
DualStack: true,
}).DialContext,
MaxIdleConns: defaultHTTPMaxIdleConns,
IdleConnTimeout: defaultHTTPIdleConnTimeout,
TLSHandshakeTimeout: defaultHTTPTLSHandshakeTimeout,
ExpectContinueTimeout: defaultHTTPExpectContinueTimeout,
}
if tlsClientConfig != nil {
transport.TLSClientConfig = tlsClientConfig
err := http2.ConfigureTransport(transport)
if err != nil {
panic(err)
}
}
return transport
}
// DefaultTLSConfig returns a new tls.Config.
func DefaultTLSConfig() *tls.Config {
return &tls.Config{
ClientSessionCache: tls.NewLRUClientSessionCache(0),
}
}
// InsecureSkipVerifyTLSConfig returns a new tls.Config which does skip TLS verification.
func InsecureSkipVerifyTLSConfig() *tls.Config {
config := DefaultTLSConfig()
config.InsecureSkipVerify = true
return config
}
// DefaultHTTPClient is a http.Client with a timeout set.
var DefaultHTTPClient = &http.Client{
Timeout: defaultHTTPTimeout,
Transport: HTTPTransportWithTLSClientConfig(DefaultTLSConfig()),
}
// InsecureHTTPClient is a http.Client with a timeout set and with TLS
// verification disabled.
var InsecureHTTPClient = &http.Client{
Timeout: defaultHTTPTimeout,
Transport: HTTPTransportWithTLSClientConfig(InsecureSkipVerifyTLSConfig()),
}
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"encoding/json"
"net/http"
)
const (
defaultJSONContentType = "application/json; encoding=utf-8"
)
// WriteJSON marshals the provided data as JSON and writes it to the provided
// http.ResponseWriter using the provided HTTP status code and content-type. the
// nature of this function is that it always writes a HTTP response header. Thus
// it makes no sense to write another header on error. Resulting errors should
// be logged and the connection should be closes as it is non-functional.
func WriteJSON(rw http.ResponseWriter, code int, data interface{}, contentType string) error {
if contentType == "" {
rw.Header().Set("Content-Type", defaultJSONContentType)
} else {
rw.Header().Set("content-Type", contentType)
}
rw.WriteHeader(code)
enc := json.NewEncoder(rw)
enc.SetIndent("", " ")
return enc.Encode(data)
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"net/http"
"net/url"
)
// OriginFromRequestHeaders tries to find information about the origin from the
// provided http.Header. It first looks into the Origin header field and if that
// is not found it looks into the Referer header field. If both are not found
// an empty string is returned.
func OriginFromRequestHeaders(header http.Header) string {
origin := header.Get("Origin")
if origin == "" {
referer := header.Get("Referer")
if referer != "" {
refererURI, _ := url.Parse(referer)
origin = refererURI.Scheme + "://" + refererURI.Host
}
}
return origin
}
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"net/http"
"net/url"
"strings"
"github.com/google/go-querystring/query"
)
// WriteRedirect crates a URL out of the provided uri and params and writes a
// a HTTP response with the provided HTTP status code to the provided
// http.ResponseWriter incliding HTTP caching headers to prevent caching. If
// asFragment is true, the provided params are added as URL fragment, otherwise
// they replace the query. If params is nil, the provided uri is taken as is.
func WriteRedirect(rw http.ResponseWriter, code int, uri *url.URL, params interface{}, asFragment bool) error {
if params != nil {
paramValues, err := query.Values(params)
if err != nil {
return err
}
u, _ := url.Parse(uri.String())
if asFragment {
if u.Fragment != "" {
u.Fragment += "&"
}
f := paramValues.Encode() // This encods into URL encoded form with QueryEscape.
f, _ = url.QueryUnescape(f) // But we need it unencoded since its the fragment, it is encoded later (when serializing the URL).
u.Fragment += f // Append fragment extension.
} else {
queryValues := u.Query()
for k, vs := range paramValues {
for _, v := range vs {
queryValues.Add(k, v)
}
}
u.RawQuery = strings.ReplaceAll(queryValues.Encode(), "+", "%20") // NOTE(longsleep): Ensure we use %20 instead of +.
}
uri = u
}
rw.Header().Set("Location", uri.String())
rw.Header().Set("Cache-Control", "no-store")
rw.Header().Set("Pragma", "no-cache")
rw.WriteHeader(code)
return nil
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"github.com/gorilla/schema"
)
// Create a Decoder instance as a package global, because it caches
// meta-data about structs, and an instance can be shared safely.
var urlSchemaDecoder = schema.NewDecoder()
// DecodeURLSchema decodes request for mdata in to the provided dst url struct.
func DecodeURLSchema(dst interface{}, src map[string][]string) error {
return urlSchemaDecoder.Decode(dst, src)
}
func init() {
urlSchemaDecoder.SetAliasTag("url")
urlSchemaDecoder.IgnoreUnknownKeys(true)
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils
import (
"net"
"net/http"
)
// IsRequestFromTrustedSource checks if the provided requests remote address is
// one either one of the provided ips or in one of the provided networks.
func IsRequestFromTrustedSource(req *http.Request, ips []*net.IP, nets []*net.IPNet) (bool, error) {
ipString, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return false, err
}
ip := net.ParseIP(ipString)
for _, checkIP := range ips {
if checkIP.Equal(ip) {
return true, nil
}
}
for _, checkNet := range nets {
if checkNet.Contains(ip) {
return true, nil
}
}
return false, nil
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2017-2019 Kopano and its licensors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package utils