summaryrefslogtreecommitdiff
path: root/vendor/github.com/vincent-petithory/dataurl/rfc2396.go
blob: e2ea0caca2ec9bd07ec693a7f1f28b2a40e66a68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package dataurl

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

// Escape implements URL escaping, as defined in RFC 2397 (http://tools.ietf.org/html/rfc2397).
// It differs a bit from net/url's QueryEscape and QueryUnescape, e.g how spaces are treated (+ instead of %20):
//
// Only ASCII chars are allowed. Reserved chars are escaped to their %xx form.
// Unreserved chars are [a-z], [A-Z], [0-9], and -_.!~*\().
func Escape(data []byte) string {
	var buf = new(bytes.Buffer)
	for _, b := range data {
		switch {
		case isUnreserved(b):
			buf.WriteByte(b)
		default:
			fmt.Fprintf(buf, "%%%02X", b)
		}
	}
	return buf.String()
}

// EscapeString is like Escape, but taking
// a string as argument.
func EscapeString(s string) string {
	return Escape([]byte(s))
}

// isUnreserved return true
// if the byte c is an unreserved char,
// as defined in RFC 2396.
func isUnreserved(c byte) bool {
	return (c >= 'a' && c <= 'z') ||
		(c >= 'A' && c <= 'Z') ||
		(c >= '0' && c <= '9') ||
		c == '-' ||
		c == '_' ||
		c == '.' ||
		c == '!' ||
		c == '~' ||
		c == '*' ||
		c == '\'' ||
		c == '(' ||
		c == ')'
}

func isHex(c byte) bool {
	switch {
	case c >= 'a' && c <= 'f':
		return true
	case c >= 'A' && c <= 'F':
		return true
	case c >= '0' && c <= '9':
		return true
	}
	return false
}

// borrowed from net/url/url.go
func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

// Unescape unescapes a character sequence
// escaped with Escape(String?).
func Unescape(s string) ([]byte, error) {
	var buf = new(bytes.Buffer)
	reader := strings.NewReader(s)

	for {
		r, size, err := reader.ReadRune()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		if size > 1 {
			return nil, fmt.Errorf("rfc2396: non-ASCII char detected")
		}

		switch r {
		case '%':
			eb1, err := reader.ReadByte()
			if err == io.EOF {
				return nil, fmt.Errorf("rfc2396: unexpected end of unescape sequence")
			}
			if err != nil {
				return nil, err
			}
			if !isHex(eb1) {
				return nil, fmt.Errorf("rfc2396: invalid char 0x%x in unescape sequence", r)
			}
			eb0, err := reader.ReadByte()
			if err == io.EOF {
				return nil, fmt.Errorf("rfc2396: unexpected end of unescape sequence")
			}
			if err != nil {
				return nil, err
			}
			if !isHex(eb0) {
				return nil, fmt.Errorf("rfc2396: invalid char 0x%x in unescape sequence", r)
			}
			buf.WriteByte(unhex(eb0) + unhex(eb1)*16)
		default:
			buf.WriteByte(byte(r))
		}
	}
	return buf.Bytes(), nil
}

// UnescapeToString is like Unescape, but returning
// a string.
func UnescapeToString(s string) (string, error) {
	b, err := Unescape(s)
	return string(b), err
}