1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package objabi
6
7import (
8	"fmt"
9	"strconv"
10	"strings"
11)
12
13// PathToPrefix converts raw string to the prefix that will be used in the
14// symbol table. All control characters, space, '%' and '"', as well as
15// non-7-bit clean bytes turn into %xx. The period needs escaping only in the
16// last segment of the path, and it makes for happier users if we escape that as
17// little as possible.
18func PathToPrefix(s string) string {
19	slash := strings.LastIndex(s, "/")
20	// check for chars that need escaping
21	n := 0
22	for r := 0; r < len(s); r++ {
23		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
24			n++
25		}
26	}
27
28	// quick exit
29	if n == 0 {
30		return s
31	}
32
33	// escape
34	const hex = "0123456789abcdef"
35	p := make([]byte, 0, len(s)+2*n)
36	for r := 0; r < len(s); r++ {
37		if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
38			p = append(p, '%', hex[c>>4], hex[c&0xF])
39		} else {
40			p = append(p, c)
41		}
42	}
43
44	return string(p)
45}
46
47// PrefixToPath is the inverse of PathToPrefix, replacing escape sequences with
48// the original character.
49func PrefixToPath(s string) (string, error) {
50	percent := strings.IndexByte(s, '%')
51	if percent == -1 {
52		return s, nil
53	}
54
55	p := make([]byte, 0, len(s))
56	for i := 0; i < len(s); {
57		if s[i] != '%' {
58			p = append(p, s[i])
59			i++
60			continue
61		}
62		if i+2 >= len(s) {
63			// Not enough characters remaining to be a valid escape
64			// sequence.
65			return "", fmt.Errorf("malformed prefix %q: escape sequence must contain two hex digits", s)
66		}
67
68		b, err := strconv.ParseUint(s[i+1:i+3], 16, 8)
69		if err != nil {
70			// Not a valid escape sequence.
71			return "", fmt.Errorf("malformed prefix %q: escape sequence %q must contain two hex digits", s, s[i:i+3])
72		}
73
74		p = append(p, byte(b))
75		i += 3
76	}
77	return string(p), nil
78}
79