blob: 456f5ce9e983669bb91ca25764cc8a0750b9953e [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// 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 common
import (
"context"
"fmt"
"html/template"
"net/url"
"sort"
"strconv"
"strings"
"google.golang.org/grpc/codes"
"go.chromium.org/luci/auth/identity"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/grpc/grpcutil"
"go.chromium.org/luci/server/auth"
)
// MergeStrings merges multiple string slices together into a single slice,
// removing duplicates.
func MergeStrings(sss ...[]string) []string {
result := []string{}
seen := map[string]bool{}
for _, ss := range sss {
for _, s := range ss {
if seen[s] {
continue
}
seen[s] = true
result = append(result, s)
}
}
sort.Strings(result)
return result
}
// ObfuscateEmail converts a string containing email address email@address.com
// into email<junk>@address.com.
func ObfuscateEmail(email string) template.HTML {
email = template.HTMLEscapeString(email)
return template.HTML(strings.Replace(
email, "@", "<span style=\"display:none\">ohnoyoudont</span>@", -1))
}
// ShortenEmail shortens Google emails.
func ShortenEmail(email string) string {
return strings.Replace(email, "@google.com", "", -1)
}
// TagGRPC annotates some gRPC with Milo specific semantics, specifically:
// * Marks the error as Unauthorized if the user is not logged in,
// and the underlying error was a 403 or 404.
// * Otherwise, tag the error with the original error code.
func TagGRPC(c context.Context, err error) error {
loggedIn := auth.CurrentIdentity(c) != identity.AnonymousIdentity
code := grpcutil.Code(err)
if code == codes.NotFound || code == codes.PermissionDenied {
// Mask the errors, so they look the same.
if loggedIn {
return errors.Reason("not found").Tag(grpcutil.NotFoundTag).Err()
}
return errors.Reason("not logged in").Tag(grpcutil.UnauthenticatedTag).Err()
}
return grpcutil.ToGRPCErr(err)
}
// ParseIntFromForm parses an integer from a form.
func ParseIntFromForm(form url.Values, key string, base int, bitSize int) (int64, error) {
input, err := ReadExactOneFromForm(form, key)
if err != nil {
return 0, err
}
ret, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return 0, errors.Annotate(err, "invalid %v; expected an integer; actual value: %v", key, input).Err()
}
return ret, nil
}
// ReadExactOneFromForm read a string from a form.
// There must be exactly one and non-empty entry of the given key in the form.
func ReadExactOneFromForm(form url.Values, key string) (string, error) {
input := form[key]
if len(input) != 1 || input[0] == "" {
return "", fmt.Errorf("multiple or missing %v; actual value: %v", key, input)
}
return input[0], nil
}
// LegacyBuilderIDString returns a legacy string identifying the builder.
// It is used in the Milo datastore.
func LegacyBuilderIDString(bid *buildbucketpb.BuilderID) string {
return fmt.Sprintf("buildbucket/luci.%s.%s/%s", bid.Project, bid.Bucket, bid.Builder)
}