Browse code

Updated vendored dependencies

Solomon Hykes authored on 2013/09/13 12:42:26
Showing 43 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+Copyright (c) 2012 The Go Authors. All rights reserved.
1
+
2
+Redistribution and use in source and binary forms, with or without
3
+modification, are permitted provided that the following conditions are
4
+met:
5
+
6
+   * Redistributions of source code must retain the above copyright
7
+notice, this list of conditions and the following disclaimer.
8
+   * Redistributions in binary form must reproduce the above
9
+copyright notice, this list of conditions and the following disclaimer
10
+in the documentation and/or other materials provided with the
11
+distribution.
12
+   * Neither the name of Google Inc. nor the names of its
13
+contributors may be used to endorse or promote products derived from
14
+this software without specific prior written permission.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 27
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+This is a fork of the upstream Go [archive/tar](http://golang.org/pkg/archive/tar/) package to add PAX header support.
1
+
2
+You can monitor the upstream pull request [here](https://codereview.appspot.com/12561043/).
0 3
new file mode 100644
... ...
@@ -0,0 +1,310 @@
0
+// Copyright 2009 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// Package tar implements access to tar archives.
5
+// It aims to cover most of the variations, including those produced
6
+// by GNU and BSD tars.
7
+//
8
+// References:
9
+//   http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
10
+//   http://www.gnu.org/software/tar/manual/html_node/Standard.html
11
+//   http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
12
+package tar
13
+
14
+import (
15
+	"bytes"
16
+	"errors"
17
+	"fmt"
18
+	"os"
19
+	"path"
20
+	"time"
21
+)
22
+
23
+const (
24
+	blockSize = 512
25
+
26
+	// Types
27
+	TypeReg           = '0'    // regular file
28
+	TypeRegA          = '\x00' // regular file
29
+	TypeLink          = '1'    // hard link
30
+	TypeSymlink       = '2'    // symbolic link
31
+	TypeChar          = '3'    // character device node
32
+	TypeBlock         = '4'    // block device node
33
+	TypeDir           = '5'    // directory
34
+	TypeFifo          = '6'    // fifo node
35
+	TypeCont          = '7'    // reserved
36
+	TypeXHeader       = 'x'    // extended header
37
+	TypeXGlobalHeader = 'g'    // global extended header
38
+	TypeGNULongName   = 'L'    // Next file has a long name
39
+	TypeGNULongLink   = 'K'    // Next file symlinks to a file w/ a long name
40
+)
41
+
42
+// A Header represents a single header in a tar archive.
43
+// Some fields may not be populated.
44
+type Header struct {
45
+	Name       string    // name of header file entry
46
+	Mode       int64     // permission and mode bits
47
+	Uid        int       // user id of owner
48
+	Gid        int       // group id of owner
49
+	Size       int64     // length in bytes
50
+	ModTime    time.Time // modified time
51
+	Typeflag   byte      // type of header entry
52
+	Linkname   string    // target name of link
53
+	Uname      string    // user name of owner
54
+	Gname      string    // group name of owner
55
+	Devmajor   int64     // major number of character or block device
56
+	Devminor   int64     // minor number of character or block device
57
+	AccessTime time.Time // access time
58
+	ChangeTime time.Time // status change time
59
+}
60
+
61
+// File name constants from the tar spec.
62
+const (
63
+	fileNameSize       = 100 // Maximum number of bytes in a standard tar name.
64
+	fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
65
+)
66
+
67
+// FileInfo returns an os.FileInfo for the Header.
68
+func (h *Header) FileInfo() os.FileInfo {
69
+	return headerFileInfo{h}
70
+}
71
+
72
+// headerFileInfo implements os.FileInfo.
73
+type headerFileInfo struct {
74
+	h *Header
75
+}
76
+
77
+func (fi headerFileInfo) Size() int64        { return fi.h.Size }
78
+func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
79
+func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
80
+func (fi headerFileInfo) Sys() interface{}   { return fi.h }
81
+
82
+// Name returns the base name of the file.
83
+func (fi headerFileInfo) Name() string {
84
+	if fi.IsDir() {
85
+		return path.Clean(fi.h.Name)
86
+	}
87
+	return fi.h.Name
88
+}
89
+
90
+// Mode returns the permission and mode bits for the headerFileInfo.
91
+func (fi headerFileInfo) Mode() (mode os.FileMode) {
92
+	// Set file permission bits.
93
+	mode = os.FileMode(fi.h.Mode).Perm()
94
+
95
+	// Set setuid, setgid and sticky bits.
96
+	if fi.h.Mode&c_ISUID != 0 {
97
+		// setuid
98
+		mode |= os.ModeSetuid
99
+	}
100
+	if fi.h.Mode&c_ISGID != 0 {
101
+		// setgid
102
+		mode |= os.ModeSetgid
103
+	}
104
+	if fi.h.Mode&c_ISVTX != 0 {
105
+		// sticky
106
+		mode |= os.ModeSticky
107
+	}
108
+
109
+	// Set file mode bits.
110
+	// clear perm, setuid, setgid and sticky bits.
111
+	m := os.FileMode(fi.h.Mode) &^ 07777
112
+	if m == c_ISDIR {
113
+		// directory
114
+		mode |= os.ModeDir
115
+	}
116
+	if m == c_ISFIFO {
117
+		// named pipe (FIFO)
118
+		mode |= os.ModeNamedPipe
119
+	}
120
+	if m == c_ISLNK {
121
+		// symbolic link
122
+		mode |= os.ModeSymlink
123
+	}
124
+	if m == c_ISBLK {
125
+		// device file
126
+		mode |= os.ModeDevice
127
+	}
128
+	if m == c_ISCHR {
129
+		// Unix character device
130
+		mode |= os.ModeDevice
131
+		mode |= os.ModeCharDevice
132
+	}
133
+	if m == c_ISSOCK {
134
+		// Unix domain socket
135
+		mode |= os.ModeSocket
136
+	}
137
+
138
+	switch fi.h.Typeflag {
139
+	case TypeLink, TypeSymlink:
140
+		// hard link, symbolic link
141
+		mode |= os.ModeSymlink
142
+	case TypeChar:
143
+		// character device node
144
+		mode |= os.ModeDevice
145
+		mode |= os.ModeCharDevice
146
+	case TypeBlock:
147
+		// block device node
148
+		mode |= os.ModeDevice
149
+	case TypeDir:
150
+		// directory
151
+		mode |= os.ModeDir
152
+	case TypeFifo:
153
+		// fifo node
154
+		mode |= os.ModeNamedPipe
155
+	}
156
+
157
+	return mode
158
+}
159
+
160
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
161
+var sysStat func(fi os.FileInfo, h *Header) error
162
+
163
+// Mode constants from the tar spec.
164
+const (
165
+	c_ISUID  = 04000   // Set uid
166
+	c_ISGID  = 02000   // Set gid
167
+	c_ISVTX  = 01000   // Save text (sticky bit)
168
+	c_ISDIR  = 040000  // Directory
169
+	c_ISFIFO = 010000  // FIFO
170
+	c_ISREG  = 0100000 // Regular file
171
+	c_ISLNK  = 0120000 // Symbolic link
172
+	c_ISBLK  = 060000  // Block special file
173
+	c_ISCHR  = 020000  // Character special file
174
+	c_ISSOCK = 0140000 // Socket
175
+)
176
+
177
+// Keywords for the PAX Extended Header
178
+const (
179
+	PAX_ATIME    = "atime"
180
+	PAX_CHARSET  = "charset"
181
+	PAX_COMMENT  = "comment"
182
+	PAX_CTIME    = "ctime" // please note that ctime is not a valid pax header.
183
+	PAX_GID      = "gid"
184
+	PAX_GNAME    = "gname"
185
+	PAX_LINKPATH = "linkpath"
186
+	PAX_MTIME    = "mtime"
187
+	PAX_PATH     = "path"
188
+	PAX_SIZE     = "size"
189
+	PAX_UID      = "uid"
190
+	PAX_UNAME    = "uname"
191
+)
192
+
193
+// FileInfoHeader creates a partially-populated Header from fi.
194
+// If fi describes a symlink, FileInfoHeader records link as the link target.
195
+// If fi describes a directory, a slash is appended to the name.
196
+func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
197
+	if fi == nil {
198
+		return nil, errors.New("tar: FileInfo is nil")
199
+	}
200
+	fm := fi.Mode()
201
+	h := &Header{
202
+		Name:    fi.Name(),
203
+		ModTime: fi.ModTime(),
204
+		Mode:    int64(fm.Perm()), // or'd with c_IS* constants later
205
+	}
206
+	switch {
207
+	case fm.IsRegular():
208
+		h.Mode |= c_ISREG
209
+		h.Typeflag = TypeReg
210
+		h.Size = fi.Size()
211
+	case fi.IsDir():
212
+		h.Typeflag = TypeDir
213
+		h.Mode |= c_ISDIR
214
+		h.Name += "/"
215
+	case fm&os.ModeSymlink != 0:
216
+		h.Typeflag = TypeSymlink
217
+		h.Mode |= c_ISLNK
218
+		h.Linkname = link
219
+	case fm&os.ModeDevice != 0:
220
+		if fm&os.ModeCharDevice != 0 {
221
+			h.Mode |= c_ISCHR
222
+			h.Typeflag = TypeChar
223
+		} else {
224
+			h.Mode |= c_ISBLK
225
+			h.Typeflag = TypeBlock
226
+		}
227
+	case fm&os.ModeNamedPipe != 0:
228
+		h.Typeflag = TypeFifo
229
+		h.Mode |= c_ISFIFO
230
+	case fm&os.ModeSocket != 0:
231
+		h.Mode |= c_ISSOCK
232
+	default:
233
+		return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
234
+	}
235
+	if fm&os.ModeSetuid != 0 {
236
+		h.Mode |= c_ISUID
237
+	}
238
+	if fm&os.ModeSetgid != 0 {
239
+		h.Mode |= c_ISGID
240
+	}
241
+	if fm&os.ModeSticky != 0 {
242
+		h.Mode |= c_ISVTX
243
+	}
244
+	if sysStat != nil {
245
+		return h, sysStat(fi, h)
246
+	}
247
+	return h, nil
248
+}
249
+
250
+var zeroBlock = make([]byte, blockSize)
251
+
252
+// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
253
+// We compute and return both.
254
+func checksum(header []byte) (unsigned int64, signed int64) {
255
+	for i := 0; i < len(header); i++ {
256
+		if i == 148 {
257
+			// The chksum field (header[148:156]) is special: it should be treated as space bytes.
258
+			unsigned += ' ' * 8
259
+			signed += ' ' * 8
260
+			i += 7
261
+			continue
262
+		}
263
+		unsigned += int64(header[i])
264
+		signed += int64(int8(header[i]))
265
+	}
266
+	return
267
+}
268
+
269
+type slicer []byte
270
+
271
+func (sp *slicer) next(n int) (b []byte) {
272
+	s := *sp
273
+	b, *sp = s[0:n], s[n:]
274
+	return
275
+}
276
+
277
+func isASCII7Bit(s string) bool {
278
+	for _, character := range s {
279
+		if (character & 0x7f) != character {
280
+			return false
281
+		}
282
+	}
283
+	return true
284
+}
285
+
286
+func stripTo7Bits(s string) string {
287
+	var buffer bytes.Buffer
288
+	for _, character := range s {
289
+		if (character & 0x7f) == character {
290
+			buffer.WriteRune(character)
291
+		}
292
+	}
293
+	return buffer.String()
294
+}
295
+
296
+func stripTo7BitsAndShorten(s string, maxLen int) string {
297
+	var buffer bytes.Buffer
298
+	count := 0
299
+	for _, character := range s {
300
+		if count == maxLen {
301
+			break
302
+		}
303
+		if (character & 0x7f) == character {
304
+			buffer.WriteRune(character)
305
+			count++
306
+		}
307
+	}
308
+	return buffer.String()
309
+}
0 310
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+// Copyright 2013 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar_test
5
+
6
+import (
7
+	"archive/tar"
8
+	"bytes"
9
+	"fmt"
10
+	"io"
11
+	"log"
12
+	"os"
13
+)
14
+
15
+func Example() {
16
+	// Create a buffer to write our archive to.
17
+	buf := new(bytes.Buffer)
18
+
19
+	// Create a new tar archive.
20
+	tw := tar.NewWriter(buf)
21
+
22
+	// Add some files to the archive.
23
+	var files = []struct {
24
+		Name, Body string
25
+	}{
26
+		{"readme.txt", "This archive contains some text files."},
27
+		{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
28
+		{"todo.txt", "Get animal handling licence."},
29
+	}
30
+	for _, file := range files {
31
+		hdr := &tar.Header{
32
+			Name: file.Name,
33
+			Size: int64(len(file.Body)),
34
+		}
35
+		if err := tw.WriteHeader(hdr); err != nil {
36
+			log.Fatalln(err)
37
+		}
38
+		if _, err := tw.Write([]byte(file.Body)); err != nil {
39
+			log.Fatalln(err)
40
+		}
41
+	}
42
+	// Make sure to check the error on Close.
43
+	if err := tw.Close(); err != nil {
44
+		log.Fatalln(err)
45
+	}
46
+
47
+	// Open the tar archive for reading.
48
+	r := bytes.NewReader(buf.Bytes())
49
+	tr := tar.NewReader(r)
50
+
51
+	// Iterate through the files in the archive.
52
+	for {
53
+		hdr, err := tr.Next()
54
+		if err == io.EOF {
55
+			// end of tar archive
56
+			break
57
+		}
58
+		if err != nil {
59
+			log.Fatalln(err)
60
+		}
61
+		fmt.Printf("Contents of %s:\n", hdr.Name)
62
+		if _, err := io.Copy(os.Stdout, tr); err != nil {
63
+			log.Fatalln(err)
64
+		}
65
+		fmt.Println()
66
+	}
67
+
68
+	// Output:
69
+	// Contents of readme.txt:
70
+	// This archive contains some text files.
71
+	// Contents of gopher.txt:
72
+	// Gopher names:
73
+	// George
74
+	// Geoffrey
75
+	// Gonzo
76
+	// Contents of todo.txt:
77
+	// Get animal handling licence.
78
+}
0 79
new file mode 100644
... ...
@@ -0,0 +1,394 @@
0
+// Copyright 2009 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar
5
+
6
+// TODO(dsymonds):
7
+//   - pax extensions
8
+
9
+import (
10
+	"bytes"
11
+	"errors"
12
+	"io"
13
+	"io/ioutil"
14
+	"os"
15
+	"strconv"
16
+	"strings"
17
+	"time"
18
+)
19
+
20
+var (
21
+	ErrHeader = errors.New("archive/tar: invalid tar header")
22
+)
23
+
24
+const maxNanoSecondIntSize = 9
25
+
26
+// A Reader provides sequential access to the contents of a tar archive.
27
+// A tar archive consists of a sequence of files.
28
+// The Next method advances to the next file in the archive (including the first),
29
+// and then it can be treated as an io.Reader to access the file's data.
30
+type Reader struct {
31
+	r   io.Reader
32
+	err error
33
+	nb  int64 // number of unread bytes for current file entry
34
+	pad int64 // amount of padding (ignored) after current file entry
35
+}
36
+
37
+// NewReader creates a new Reader reading from r.
38
+func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
39
+
40
+// Next advances to the next entry in the tar archive.
41
+func (tr *Reader) Next() (*Header, error) {
42
+	var hdr *Header
43
+	if tr.err == nil {
44
+		tr.skipUnread()
45
+	}
46
+	if tr.err != nil {
47
+		return hdr, tr.err
48
+	}
49
+	hdr = tr.readHeader()
50
+	if hdr == nil {
51
+		return hdr, tr.err
52
+	}
53
+	// Check for PAX/GNU header.
54
+	switch hdr.Typeflag {
55
+	case TypeXHeader:
56
+		//  PAX extended header
57
+		headers, err := parsePAX(tr)
58
+		if err != nil {
59
+			return nil, err
60
+		}
61
+		// We actually read the whole file,
62
+		// but this skips alignment padding
63
+		tr.skipUnread()
64
+		hdr = tr.readHeader()
65
+		mergePAX(hdr, headers)
66
+		return hdr, nil
67
+	case TypeGNULongName:
68
+		// We have a GNU long name header. Its contents are the real file name.
69
+		realname, err := ioutil.ReadAll(tr)
70
+		if err != nil {
71
+			return nil, err
72
+		}
73
+		hdr, err := tr.Next()
74
+		hdr.Name = cString(realname)
75
+		return hdr, err
76
+	case TypeGNULongLink:
77
+		// We have a GNU long link header.
78
+		realname, err := ioutil.ReadAll(tr)
79
+		if err != nil {
80
+			return nil, err
81
+		}
82
+		hdr, err := tr.Next()
83
+		hdr.Linkname = cString(realname)
84
+		return hdr, err
85
+	}
86
+	return hdr, tr.err
87
+}
88
+
89
+// mergePAX merges well known headers according to PAX standard.
90
+// In general headers with the same name as those found
91
+// in the header struct overwrite those found in the header
92
+// struct with higher precision or longer values. Esp. useful
93
+// for name and linkname fields.
94
+func mergePAX(hdr *Header, headers map[string]string) error {
95
+	for k, v := range headers {
96
+		switch k {
97
+		case PAX_PATH:
98
+			hdr.Name = v
99
+		case PAX_LINKPATH:
100
+			hdr.Linkname = v
101
+		case PAX_GNAME:
102
+			hdr.Gname = v
103
+		case PAX_UNAME:
104
+			hdr.Uname = v
105
+		case PAX_UID:
106
+			uid, err := strconv.ParseInt(v, 10, 0)
107
+			if err != nil {
108
+				return err
109
+			}
110
+			hdr.Uid = int(uid)
111
+		case PAX_GID:
112
+			gid, err := strconv.ParseInt(v, 10, 0)
113
+			if err != nil {
114
+				return err
115
+			}
116
+			hdr.Gid = int(gid)
117
+		case PAX_ATIME:
118
+			t, err := parsePAXTime(v)
119
+			if err != nil {
120
+				return err
121
+			}
122
+			hdr.AccessTime = t
123
+		case PAX_MTIME:
124
+			t, err := parsePAXTime(v)
125
+			if err != nil {
126
+				return err
127
+			}
128
+			hdr.ModTime = t
129
+		case PAX_CTIME:
130
+			t, err := parsePAXTime(v)
131
+			if err != nil {
132
+				return err
133
+			}
134
+			hdr.ChangeTime = t
135
+		case PAX_SIZE:
136
+			size, err := strconv.ParseInt(v, 10, 0)
137
+			if err != nil {
138
+				return err
139
+			}
140
+			hdr.Size = int64(size)
141
+		}
142
+
143
+	}
144
+	return nil
145
+}
146
+
147
+// parsePAXTime takes a string of the form %d.%d as described in
148
+// the PAX specification.
149
+func parsePAXTime(t string) (time.Time, error) {
150
+	buf := []byte(t)
151
+	pos := bytes.IndexByte(buf, '.')
152
+	var seconds, nanoseconds int64
153
+	var err error
154
+	if pos == -1 {
155
+		seconds, err = strconv.ParseInt(t, 10, 0)
156
+		if err != nil {
157
+			return time.Time{}, err
158
+		}
159
+	} else {
160
+		seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0)
161
+		if err != nil {
162
+			return time.Time{}, err
163
+		}
164
+		nano_buf := string(buf[pos+1:])
165
+		// Pad as needed before converting to a decimal.
166
+		// For example .030 -> .030000000 -> 30000000 nanoseconds
167
+		if len(nano_buf) < maxNanoSecondIntSize {
168
+			// Right pad
169
+			nano_buf += strings.Repeat("0", maxNanoSecondIntSize-len(nano_buf))
170
+		} else if len(nano_buf) > maxNanoSecondIntSize {
171
+			// Right truncate
172
+			nano_buf = nano_buf[:maxNanoSecondIntSize]
173
+		}
174
+		nanoseconds, err = strconv.ParseInt(string(nano_buf), 10, 0)
175
+		if err != nil {
176
+			return time.Time{}, err
177
+		}
178
+	}
179
+	ts := time.Unix(seconds, nanoseconds)
180
+	return ts, nil
181
+}
182
+
183
+// parsePAX parses PAX headers.
184
+// If an extended header (type 'x') is invalid, ErrHeader is returned
185
+func parsePAX(r io.Reader) (map[string]string, error) {
186
+	buf, err := ioutil.ReadAll(r)
187
+	if err != nil {
188
+		return nil, err
189
+	}
190
+	headers := make(map[string]string)
191
+	// Each record is constructed as
192
+	//     "%d %s=%s\n", length, keyword, value
193
+	for len(buf) > 0 {
194
+		// or the header was empty to start with.
195
+		var sp int
196
+		// The size field ends at the first space.
197
+		sp = bytes.IndexByte(buf, ' ')
198
+		if sp == -1 {
199
+			return nil, ErrHeader
200
+		}
201
+		// Parse the first token as a decimal integer.
202
+		n, err := strconv.ParseInt(string(buf[:sp]), 10, 0)
203
+		if err != nil {
204
+			return nil, ErrHeader
205
+		}
206
+		// Extract everything between the decimal and the n -1 on the
207
+		// beginning to to eat the ' ', -1 on the end to skip the newline.
208
+		var record []byte
209
+		record, buf = buf[sp+1:n-1], buf[n:]
210
+		// The first equals is guaranteed to mark the end of the key.
211
+		// Everything else is value.
212
+		eq := bytes.IndexByte(record, '=')
213
+		if eq == -1 {
214
+			return nil, ErrHeader
215
+		}
216
+		key, value := record[:eq], record[eq+1:]
217
+		headers[string(key)] = string(value)
218
+	}
219
+	return headers, nil
220
+}
221
+
222
+// cString parses bytes as a NUL-terminated C-style string.
223
+// If a NUL byte is not found then the whole slice is returned as a string.
224
+func cString(b []byte) string {
225
+	n := 0
226
+	for n < len(b) && b[n] != 0 {
227
+		n++
228
+	}
229
+	return string(b[0:n])
230
+}
231
+
232
+func (tr *Reader) octal(b []byte) int64 {
233
+	// Check for binary format first.
234
+	if len(b) > 0 && b[0]&0x80 != 0 {
235
+		var x int64
236
+		for i, c := range b {
237
+			if i == 0 {
238
+				c &= 0x7f // ignore signal bit in first byte
239
+			}
240
+			x = x<<8 | int64(c)
241
+		}
242
+		return x
243
+	}
244
+
245
+	// Removing leading spaces.
246
+	for len(b) > 0 && b[0] == ' ' {
247
+		b = b[1:]
248
+	}
249
+	// Removing trailing NULs and spaces.
250
+	for len(b) > 0 && (b[len(b)-1] == ' ' || b[len(b)-1] == '\x00') {
251
+		b = b[0 : len(b)-1]
252
+	}
253
+	x, err := strconv.ParseUint(cString(b), 8, 64)
254
+	if err != nil {
255
+		tr.err = err
256
+	}
257
+	return int64(x)
258
+}
259
+
260
+// skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding.
261
+func (tr *Reader) skipUnread() {
262
+	nr := tr.nb + tr.pad // number of bytes to skip
263
+	tr.nb, tr.pad = 0, 0
264
+	if sr, ok := tr.r.(io.Seeker); ok {
265
+		if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil {
266
+			return
267
+		}
268
+	}
269
+	_, tr.err = io.CopyN(ioutil.Discard, tr.r, nr)
270
+}
271
+
272
+func (tr *Reader) verifyChecksum(header []byte) bool {
273
+	if tr.err != nil {
274
+		return false
275
+	}
276
+
277
+	given := tr.octal(header[148:156])
278
+	unsigned, signed := checksum(header)
279
+	return given == unsigned || given == signed
280
+}
281
+
282
+func (tr *Reader) readHeader() *Header {
283
+	header := make([]byte, blockSize)
284
+	if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
285
+		return nil
286
+	}
287
+
288
+	// Two blocks of zero bytes marks the end of the archive.
289
+	if bytes.Equal(header, zeroBlock[0:blockSize]) {
290
+		if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil {
291
+			return nil
292
+		}
293
+		if bytes.Equal(header, zeroBlock[0:blockSize]) {
294
+			tr.err = io.EOF
295
+		} else {
296
+			tr.err = ErrHeader // zero block and then non-zero block
297
+		}
298
+		return nil
299
+	}
300
+
301
+	if !tr.verifyChecksum(header) {
302
+		tr.err = ErrHeader
303
+		return nil
304
+	}
305
+
306
+	// Unpack
307
+	hdr := new(Header)
308
+	s := slicer(header)
309
+
310
+	hdr.Name = cString(s.next(100))
311
+	hdr.Mode = tr.octal(s.next(8))
312
+	hdr.Uid = int(tr.octal(s.next(8)))
313
+	hdr.Gid = int(tr.octal(s.next(8)))
314
+	hdr.Size = tr.octal(s.next(12))
315
+	hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0)
316
+	s.next(8) // chksum
317
+	hdr.Typeflag = s.next(1)[0]
318
+	hdr.Linkname = cString(s.next(100))
319
+
320
+	// The remainder of the header depends on the value of magic.
321
+	// The original (v7) version of tar had no explicit magic field,
322
+	// so its magic bytes, like the rest of the block, are NULs.
323
+	magic := string(s.next(8)) // contains version field as well.
324
+	var format string
325
+	switch magic {
326
+	case "ustar\x0000": // POSIX tar (1003.1-1988)
327
+		if string(header[508:512]) == "tar\x00" {
328
+			format = "star"
329
+		} else {
330
+			format = "posix"
331
+		}
332
+	case "ustar  \x00": // old GNU tar
333
+		format = "gnu"
334
+	}
335
+
336
+	switch format {
337
+	case "posix", "gnu", "star":
338
+		hdr.Uname = cString(s.next(32))
339
+		hdr.Gname = cString(s.next(32))
340
+		devmajor := s.next(8)
341
+		devminor := s.next(8)
342
+		if hdr.Typeflag == TypeChar || hdr.Typeflag == TypeBlock {
343
+			hdr.Devmajor = tr.octal(devmajor)
344
+			hdr.Devminor = tr.octal(devminor)
345
+		}
346
+		var prefix string
347
+		switch format {
348
+		case "posix", "gnu":
349
+			prefix = cString(s.next(155))
350
+		case "star":
351
+			prefix = cString(s.next(131))
352
+			hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0)
353
+			hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0)
354
+		}
355
+		if len(prefix) > 0 {
356
+			hdr.Name = prefix + "/" + hdr.Name
357
+		}
358
+	}
359
+
360
+	if tr.err != nil {
361
+		tr.err = ErrHeader
362
+		return nil
363
+	}
364
+
365
+	// Maximum value of hdr.Size is 64 GB (12 octal digits),
366
+	// so there's no risk of int64 overflowing.
367
+	tr.nb = int64(hdr.Size)
368
+	tr.pad = -tr.nb & (blockSize - 1) // blockSize is a power of two
369
+
370
+	return hdr
371
+}
372
+
373
+// Read reads from the current entry in the tar archive.
374
+// It returns 0, io.EOF when it reaches the end of that entry,
375
+// until Next is called to advance to the next entry.
376
+func (tr *Reader) Read(b []byte) (n int, err error) {
377
+	if tr.nb == 0 {
378
+		// file consumed
379
+		return 0, io.EOF
380
+	}
381
+
382
+	if int64(len(b)) > tr.nb {
383
+		b = b[0:tr.nb]
384
+	}
385
+	n, err = tr.r.Read(b)
386
+	tr.nb -= int64(n)
387
+
388
+	if err == io.EOF && tr.nb > 0 {
389
+		err = io.ErrUnexpectedEOF
390
+	}
391
+	tr.err = err
392
+	return
393
+}
0 394
new file mode 100644
... ...
@@ -0,0 +1,366 @@
0
+// Copyright 2009 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar
5
+
6
+import (
7
+	"bytes"
8
+	"crypto/md5"
9
+	"fmt"
10
+	"io"
11
+	"os"
12
+	"reflect"
13
+	"strings"
14
+	"testing"
15
+	"time"
16
+)
17
+
18
+type untarTest struct {
19
+	file    string
20
+	headers []*Header
21
+	cksums  []string
22
+}
23
+
24
+var gnuTarTest = &untarTest{
25
+	file: "testdata/gnu.tar",
26
+	headers: []*Header{
27
+		{
28
+			Name:     "small.txt",
29
+			Mode:     0640,
30
+			Uid:      73025,
31
+			Gid:      5000,
32
+			Size:     5,
33
+			ModTime:  time.Unix(1244428340, 0),
34
+			Typeflag: '0',
35
+			Uname:    "dsymonds",
36
+			Gname:    "eng",
37
+		},
38
+		{
39
+			Name:     "small2.txt",
40
+			Mode:     0640,
41
+			Uid:      73025,
42
+			Gid:      5000,
43
+			Size:     11,
44
+			ModTime:  time.Unix(1244436044, 0),
45
+			Typeflag: '0',
46
+			Uname:    "dsymonds",
47
+			Gname:    "eng",
48
+		},
49
+	},
50
+	cksums: []string{
51
+		"e38b27eaccb4391bdec553a7f3ae6b2f",
52
+		"c65bd2e50a56a2138bf1716f2fd56fe9",
53
+	},
54
+}
55
+
56
+var untarTests = []*untarTest{
57
+	gnuTarTest,
58
+	{
59
+		file: "testdata/star.tar",
60
+		headers: []*Header{
61
+			{
62
+				Name:       "small.txt",
63
+				Mode:       0640,
64
+				Uid:        73025,
65
+				Gid:        5000,
66
+				Size:       5,
67
+				ModTime:    time.Unix(1244592783, 0),
68
+				Typeflag:   '0',
69
+				Uname:      "dsymonds",
70
+				Gname:      "eng",
71
+				AccessTime: time.Unix(1244592783, 0),
72
+				ChangeTime: time.Unix(1244592783, 0),
73
+			},
74
+			{
75
+				Name:       "small2.txt",
76
+				Mode:       0640,
77
+				Uid:        73025,
78
+				Gid:        5000,
79
+				Size:       11,
80
+				ModTime:    time.Unix(1244592783, 0),
81
+				Typeflag:   '0',
82
+				Uname:      "dsymonds",
83
+				Gname:      "eng",
84
+				AccessTime: time.Unix(1244592783, 0),
85
+				ChangeTime: time.Unix(1244592783, 0),
86
+			},
87
+		},
88
+	},
89
+	{
90
+		file: "testdata/v7.tar",
91
+		headers: []*Header{
92
+			{
93
+				Name:     "small.txt",
94
+				Mode:     0444,
95
+				Uid:      73025,
96
+				Gid:      5000,
97
+				Size:     5,
98
+				ModTime:  time.Unix(1244593104, 0),
99
+				Typeflag: '\x00',
100
+			},
101
+			{
102
+				Name:     "small2.txt",
103
+				Mode:     0444,
104
+				Uid:      73025,
105
+				Gid:      5000,
106
+				Size:     11,
107
+				ModTime:  time.Unix(1244593104, 0),
108
+				Typeflag: '\x00',
109
+			},
110
+		},
111
+	},
112
+	{
113
+		file: "testdata/pax.tar",
114
+		headers: []*Header{
115
+			{
116
+				Name:       "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
117
+				Mode:       0664,
118
+				Uid:        1000,
119
+				Gid:        1000,
120
+				Uname:      "shane",
121
+				Gname:      "shane",
122
+				Size:       7,
123
+				ModTime:    time.Unix(1350244992, 23960108),
124
+				ChangeTime: time.Unix(1350244992, 23960108),
125
+				AccessTime: time.Unix(1350244992, 23960108),
126
+				Typeflag:   TypeReg,
127
+			},
128
+			{
129
+				Name:       "a/b",
130
+				Mode:       0777,
131
+				Uid:        1000,
132
+				Gid:        1000,
133
+				Uname:      "shane",
134
+				Gname:      "shane",
135
+				Size:       0,
136
+				ModTime:    time.Unix(1350266320, 910238425),
137
+				ChangeTime: time.Unix(1350266320, 910238425),
138
+				AccessTime: time.Unix(1350266320, 910238425),
139
+				Typeflag:   TypeSymlink,
140
+				Linkname:   "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
141
+			},
142
+		},
143
+	},
144
+}
145
+
146
+func TestReader(t *testing.T) {
147
+testLoop:
148
+	for i, test := range untarTests {
149
+		f, err := os.Open(test.file)
150
+		if err != nil {
151
+			t.Errorf("test %d: Unexpected error: %v", i, err)
152
+			continue
153
+		}
154
+		tr := NewReader(f)
155
+		for j, header := range test.headers {
156
+			hdr, err := tr.Next()
157
+			if err != nil || hdr == nil {
158
+				t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err)
159
+				f.Close()
160
+				continue testLoop
161
+			}
162
+			if *hdr != *header {
163
+				t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
164
+					i, j, *hdr, *header)
165
+			}
166
+		}
167
+		hdr, err := tr.Next()
168
+		if err == io.EOF {
169
+			continue testLoop
170
+		}
171
+		if hdr != nil || err != nil {
172
+			t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, hdr, err)
173
+		}
174
+		f.Close()
175
+	}
176
+}
177
+
178
+func TestPartialRead(t *testing.T) {
179
+	f, err := os.Open("testdata/gnu.tar")
180
+	if err != nil {
181
+		t.Fatalf("Unexpected error: %v", err)
182
+	}
183
+	defer f.Close()
184
+
185
+	tr := NewReader(f)
186
+
187
+	// Read the first four bytes; Next() should skip the last byte.
188
+	hdr, err := tr.Next()
189
+	if err != nil || hdr == nil {
190
+		t.Fatalf("Didn't get first file: %v", err)
191
+	}
192
+	buf := make([]byte, 4)
193
+	if _, err := io.ReadFull(tr, buf); err != nil {
194
+		t.Fatalf("Unexpected error: %v", err)
195
+	}
196
+	if expected := []byte("Kilt"); !bytes.Equal(buf, expected) {
197
+		t.Errorf("Contents = %v, want %v", buf, expected)
198
+	}
199
+
200
+	// Second file
201
+	hdr, err = tr.Next()
202
+	if err != nil || hdr == nil {
203
+		t.Fatalf("Didn't get second file: %v", err)
204
+	}
205
+	buf = make([]byte, 6)
206
+	if _, err := io.ReadFull(tr, buf); err != nil {
207
+		t.Fatalf("Unexpected error: %v", err)
208
+	}
209
+	if expected := []byte("Google"); !bytes.Equal(buf, expected) {
210
+		t.Errorf("Contents = %v, want %v", buf, expected)
211
+	}
212
+}
213
+
214
+func TestIncrementalRead(t *testing.T) {
215
+	test := gnuTarTest
216
+	f, err := os.Open(test.file)
217
+	if err != nil {
218
+		t.Fatalf("Unexpected error: %v", err)
219
+	}
220
+	defer f.Close()
221
+
222
+	tr := NewReader(f)
223
+
224
+	headers := test.headers
225
+	cksums := test.cksums
226
+	nread := 0
227
+
228
+	// loop over all files
229
+	for ; ; nread++ {
230
+		hdr, err := tr.Next()
231
+		if hdr == nil || err == io.EOF {
232
+			break
233
+		}
234
+
235
+		// check the header
236
+		if *hdr != *headers[nread] {
237
+			t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
238
+				*hdr, headers[nread])
239
+		}
240
+
241
+		// read file contents in little chunks EOF,
242
+		// checksumming all the way
243
+		h := md5.New()
244
+		rdbuf := make([]uint8, 8)
245
+		for {
246
+			nr, err := tr.Read(rdbuf)
247
+			if err == io.EOF {
248
+				break
249
+			}
250
+			if err != nil {
251
+				t.Errorf("Read: unexpected error %v\n", err)
252
+				break
253
+			}
254
+			h.Write(rdbuf[0:nr])
255
+		}
256
+		// verify checksum
257
+		have := fmt.Sprintf("%x", h.Sum(nil))
258
+		want := cksums[nread]
259
+		if want != have {
260
+			t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want)
261
+		}
262
+	}
263
+	if nread != len(headers) {
264
+		t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread)
265
+	}
266
+}
267
+
268
+func TestNonSeekable(t *testing.T) {
269
+	test := gnuTarTest
270
+	f, err := os.Open(test.file)
271
+	if err != nil {
272
+		t.Fatalf("Unexpected error: %v", err)
273
+	}
274
+	defer f.Close()
275
+
276
+	type readerOnly struct {
277
+		io.Reader
278
+	}
279
+	tr := NewReader(readerOnly{f})
280
+	nread := 0
281
+
282
+	for ; ; nread++ {
283
+		_, err := tr.Next()
284
+		if err == io.EOF {
285
+			break
286
+		}
287
+		if err != nil {
288
+			t.Fatalf("Unexpected error: %v", err)
289
+		}
290
+	}
291
+
292
+	if nread != len(test.headers) {
293
+		t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(test.headers), nread)
294
+	}
295
+}
296
+
297
+func TestParsePAXHeader(t *testing.T) {
298
+	paxTests := [][3]string{
299
+		{"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths
300
+		{"a", "a=name", "9 a=name\n"},  // Test case involving multiple acceptable length
301
+		{"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}}
302
+	for _, test := range paxTests {
303
+		key, expected, raw := test[0], test[1], test[2]
304
+		reader := bytes.NewBuffer([]byte(raw))
305
+		headers, err := parsePAX(reader)
306
+		if err != nil {
307
+			t.Errorf("Couldn't parse correctly formatted headers: %v", err)
308
+			continue
309
+		}
310
+		if strings.EqualFold(headers[key], expected) {
311
+			t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected)
312
+			continue
313
+		}
314
+		trailer := make([]byte, 100)
315
+		n, err := reader.Read(trailer)
316
+		if err != io.EOF || n != 0 {
317
+			t.Error("Buffer wasn't consumed")
318
+		}
319
+	}
320
+	badHeader := bytes.NewBuffer([]byte("3 somelongkey="))
321
+	if _, err := parsePAX(badHeader); err != ErrHeader {
322
+		t.Fatal("Unexpected success when parsing bad header")
323
+	}
324
+}
325
+
326
+func TestParsePAXTime(t *testing.T) {
327
+	// Some valid PAX time values
328
+	timestamps := map[string]time.Time{
329
+		"1350244992.023960108":  time.Unix(1350244992, 23960108), // The commoon case
330
+		"1350244992.02396010":   time.Unix(1350244992, 23960100), // Lower precision value
331
+		"1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
332
+		"1350244992":            time.Unix(1350244992, 0),        // Low precision value
333
+	}
334
+	for input, expected := range timestamps {
335
+		ts, err := parsePAXTime(input)
336
+		if err != nil {
337
+			t.Fatal(err)
338
+		}
339
+		if !ts.Equal(expected) {
340
+			t.Fatalf("Time parsing failure %s %s", ts, expected)
341
+		}
342
+	}
343
+}
344
+
345
+func TestMergePAX(t *testing.T) {
346
+	hdr := new(Header)
347
+	// Test a string, integer, and time based value.
348
+	headers := map[string]string{
349
+		"path":  "a/b/c",
350
+		"uid":   "1000",
351
+		"mtime": "1350244992.023960108",
352
+	}
353
+	err := mergePAX(hdr, headers)
354
+	if err != nil {
355
+		t.Fatal(err)
356
+	}
357
+	want := &Header{
358
+		Name:    "a/b/c",
359
+		Uid:     1000,
360
+		ModTime: time.Unix(1350244992, 23960108),
361
+	}
362
+	if !reflect.DeepEqual(hdr, want) {
363
+		t.Errorf("incorrect merge: got %+v, want %+v", hdr, want)
364
+	}
365
+}
0 366
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build linux openbsd
5
+
6
+package tar
7
+
8
+import (
9
+	"syscall"
10
+	"time"
11
+)
12
+
13
+func statAtime(st *syscall.Stat_t) time.Time {
14
+	return time.Unix(st.Atim.Unix())
15
+}
16
+
17
+func statCtime(st *syscall.Stat_t) time.Time {
18
+	return time.Unix(st.Ctim.Unix())
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build darwin freebsd netbsd
5
+
6
+package tar
7
+
8
+import (
9
+	"syscall"
10
+	"time"
11
+)
12
+
13
+func statAtime(st *syscall.Stat_t) time.Time {
14
+	return time.Unix(st.Atimespec.Unix())
15
+}
16
+
17
+func statCtime(st *syscall.Stat_t) time.Time {
18
+	return time.Unix(st.Ctimespec.Unix())
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build linux darwin freebsd openbsd netbsd
5
+
6
+package tar
7
+
8
+import (
9
+	"os"
10
+	"syscall"
11
+)
12
+
13
+func init() {
14
+	sysStat = statUnix
15
+}
16
+
17
+func statUnix(fi os.FileInfo, h *Header) error {
18
+	sys, ok := fi.Sys().(*syscall.Stat_t)
19
+	if !ok {
20
+		return nil
21
+	}
22
+	h.Uid = int(sys.Uid)
23
+	h.Gid = int(sys.Gid)
24
+	// TODO(bradfitz): populate username & group.  os/user
25
+	// doesn't cache LookupId lookups, and lacks group
26
+	// lookup functions.
27
+	h.AccessTime = statAtime(sys)
28
+	h.ChangeTime = statCtime(sys)
29
+	// TODO(bradfitz): major/minor device numbers?
30
+	return nil
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,271 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar
5
+
6
+import (
7
+	"bytes"
8
+	"io/ioutil"
9
+	"os"
10
+	"reflect"
11
+	"testing"
12
+	"time"
13
+)
14
+
15
+func TestFileInfoHeader(t *testing.T) {
16
+	fi, err := os.Stat("testdata/small.txt")
17
+	if err != nil {
18
+		t.Fatal(err)
19
+	}
20
+	h, err := FileInfoHeader(fi, "")
21
+	if err != nil {
22
+		t.Fatalf("FileInfoHeader: %v", err)
23
+	}
24
+	if g, e := h.Name, "small.txt"; g != e {
25
+		t.Errorf("Name = %q; want %q", g, e)
26
+	}
27
+	if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
28
+		t.Errorf("Mode = %#o; want %#o", g, e)
29
+	}
30
+	if g, e := h.Size, int64(5); g != e {
31
+		t.Errorf("Size = %v; want %v", g, e)
32
+	}
33
+	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
34
+		t.Errorf("ModTime = %v; want %v", g, e)
35
+	}
36
+}
37
+
38
+func TestFileInfoHeaderDir(t *testing.T) {
39
+	fi, err := os.Stat("testdata")
40
+	if err != nil {
41
+		t.Fatal(err)
42
+	}
43
+	h, err := FileInfoHeader(fi, "")
44
+	if err != nil {
45
+		t.Fatalf("FileInfoHeader: %v", err)
46
+	}
47
+	if g, e := h.Name, "testdata/"; g != e {
48
+		t.Errorf("Name = %q; want %q", g, e)
49
+	}
50
+	// Ignoring c_ISGID for golang.org/issue/4867
51
+	if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
52
+		t.Errorf("Mode = %#o; want %#o", g, e)
53
+	}
54
+	if g, e := h.Size, int64(0); g != e {
55
+		t.Errorf("Size = %v; want %v", g, e)
56
+	}
57
+	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
58
+		t.Errorf("ModTime = %v; want %v", g, e)
59
+	}
60
+}
61
+
62
+func TestFileInfoHeaderSymlink(t *testing.T) {
63
+	h, err := FileInfoHeader(symlink{}, "some-target")
64
+	if err != nil {
65
+		t.Fatal(err)
66
+	}
67
+	if g, e := h.Name, "some-symlink"; g != e {
68
+		t.Errorf("Name = %q; want %q", g, e)
69
+	}
70
+	if g, e := h.Linkname, "some-target"; g != e {
71
+		t.Errorf("Linkname = %q; want %q", g, e)
72
+	}
73
+}
74
+
75
+type symlink struct{}
76
+
77
+func (symlink) Name() string       { return "some-symlink" }
78
+func (symlink) Size() int64        { return 0 }
79
+func (symlink) Mode() os.FileMode  { return os.ModeSymlink }
80
+func (symlink) ModTime() time.Time { return time.Time{} }
81
+func (symlink) IsDir() bool        { return false }
82
+func (symlink) Sys() interface{}   { return nil }
83
+
84
+func TestRoundTrip(t *testing.T) {
85
+	data := []byte("some file contents")
86
+
87
+	var b bytes.Buffer
88
+	tw := NewWriter(&b)
89
+	hdr := &Header{
90
+		Name:    "file.txt",
91
+		Uid:     1 << 21, // too big for 8 octal digits
92
+		Size:    int64(len(data)),
93
+		ModTime: time.Now(),
94
+	}
95
+	// tar only supports second precision.
96
+	hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
97
+	if err := tw.WriteHeader(hdr); err != nil {
98
+		t.Fatalf("tw.WriteHeader: %v", err)
99
+	}
100
+	if _, err := tw.Write(data); err != nil {
101
+		t.Fatalf("tw.Write: %v", err)
102
+	}
103
+	if err := tw.Close(); err != nil {
104
+		t.Fatalf("tw.Close: %v", err)
105
+	}
106
+
107
+	// Read it back.
108
+	tr := NewReader(&b)
109
+	rHdr, err := tr.Next()
110
+	if err != nil {
111
+		t.Fatalf("tr.Next: %v", err)
112
+	}
113
+	if !reflect.DeepEqual(rHdr, hdr) {
114
+		t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
115
+	}
116
+	rData, err := ioutil.ReadAll(tr)
117
+	if err != nil {
118
+		t.Fatalf("Read: %v", err)
119
+	}
120
+	if !bytes.Equal(rData, data) {
121
+		t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
122
+	}
123
+}
124
+
125
+type headerRoundTripTest struct {
126
+	h  *Header
127
+	fm os.FileMode
128
+}
129
+
130
+func TestHeaderRoundTrip(t *testing.T) {
131
+	golden := []headerRoundTripTest{
132
+		// regular file.
133
+		{
134
+			h: &Header{
135
+				Name:     "test.txt",
136
+				Mode:     0644 | c_ISREG,
137
+				Size:     12,
138
+				ModTime:  time.Unix(1360600916, 0),
139
+				Typeflag: TypeReg,
140
+			},
141
+			fm: 0644,
142
+		},
143
+		// hard link.
144
+		{
145
+			h: &Header{
146
+				Name:     "hard.txt",
147
+				Mode:     0644 | c_ISLNK,
148
+				Size:     0,
149
+				ModTime:  time.Unix(1360600916, 0),
150
+				Typeflag: TypeLink,
151
+			},
152
+			fm: 0644 | os.ModeSymlink,
153
+		},
154
+		// symbolic link.
155
+		{
156
+			h: &Header{
157
+				Name:     "link.txt",
158
+				Mode:     0777 | c_ISLNK,
159
+				Size:     0,
160
+				ModTime:  time.Unix(1360600852, 0),
161
+				Typeflag: TypeSymlink,
162
+			},
163
+			fm: 0777 | os.ModeSymlink,
164
+		},
165
+		// character device node.
166
+		{
167
+			h: &Header{
168
+				Name:     "dev/null",
169
+				Mode:     0666 | c_ISCHR,
170
+				Size:     0,
171
+				ModTime:  time.Unix(1360578951, 0),
172
+				Typeflag: TypeChar,
173
+			},
174
+			fm: 0666 | os.ModeDevice | os.ModeCharDevice,
175
+		},
176
+		// block device node.
177
+		{
178
+			h: &Header{
179
+				Name:     "dev/sda",
180
+				Mode:     0660 | c_ISBLK,
181
+				Size:     0,
182
+				ModTime:  time.Unix(1360578954, 0),
183
+				Typeflag: TypeBlock,
184
+			},
185
+			fm: 0660 | os.ModeDevice,
186
+		},
187
+		// directory.
188
+		{
189
+			h: &Header{
190
+				Name:     "dir/",
191
+				Mode:     0755 | c_ISDIR,
192
+				Size:     0,
193
+				ModTime:  time.Unix(1360601116, 0),
194
+				Typeflag: TypeDir,
195
+			},
196
+			fm: 0755 | os.ModeDir,
197
+		},
198
+		// fifo node.
199
+		{
200
+			h: &Header{
201
+				Name:     "dev/initctl",
202
+				Mode:     0600 | c_ISFIFO,
203
+				Size:     0,
204
+				ModTime:  time.Unix(1360578949, 0),
205
+				Typeflag: TypeFifo,
206
+			},
207
+			fm: 0600 | os.ModeNamedPipe,
208
+		},
209
+		// setuid.
210
+		{
211
+			h: &Header{
212
+				Name:     "bin/su",
213
+				Mode:     0755 | c_ISREG | c_ISUID,
214
+				Size:     23232,
215
+				ModTime:  time.Unix(1355405093, 0),
216
+				Typeflag: TypeReg,
217
+			},
218
+			fm: 0755 | os.ModeSetuid,
219
+		},
220
+		// setguid.
221
+		{
222
+			h: &Header{
223
+				Name:     "group.txt",
224
+				Mode:     0750 | c_ISREG | c_ISGID,
225
+				Size:     0,
226
+				ModTime:  time.Unix(1360602346, 0),
227
+				Typeflag: TypeReg,
228
+			},
229
+			fm: 0750 | os.ModeSetgid,
230
+		},
231
+		// sticky.
232
+		{
233
+			h: &Header{
234
+				Name:     "sticky.txt",
235
+				Mode:     0600 | c_ISREG | c_ISVTX,
236
+				Size:     7,
237
+				ModTime:  time.Unix(1360602540, 0),
238
+				Typeflag: TypeReg,
239
+			},
240
+			fm: 0600 | os.ModeSticky,
241
+		},
242
+	}
243
+
244
+	for i, g := range golden {
245
+		fi := g.h.FileInfo()
246
+		h2, err := FileInfoHeader(fi, "")
247
+		if err != nil {
248
+			t.Error(err)
249
+			continue
250
+		}
251
+		if got, want := h2.Name, g.h.Name; got != want {
252
+			t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
253
+		}
254
+		if got, want := h2.Size, g.h.Size; got != want {
255
+			t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
256
+		}
257
+		if got, want := h2.Mode, g.h.Mode; got != want {
258
+			t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
259
+		}
260
+		if got, want := fi.Mode(), g.fm; got != want {
261
+			t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
262
+		}
263
+		if got, want := h2.ModTime, g.h.ModTime; got != want {
264
+			t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
265
+		}
266
+		if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
267
+			t.Errorf("i=%d: Sys didn't return original *Header", i)
268
+		}
269
+	}
270
+}
0 271
new file mode 100644
1 272
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/gnu.tar differ
2 273
new file mode 100644
3 274
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/pax.tar differ
4 275
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Kilts
0 1
\ No newline at end of file
1 2
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Google.com
0 1
new file mode 100644
1 2
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/star.tar differ
2 3
new file mode 100644
3 4
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/ustar.tar differ
4 5
new file mode 100644
5 6
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/v7.tar differ
6 7
new file mode 100644
7 8
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/writer-big.tar differ
8 9
new file mode 100644
9 10
Binary files /dev/null and b/vendor/src/github.com/dotcloud/tar/testdata/writer.tar differ
10 11
new file mode 100644
... ...
@@ -0,0 +1,390 @@
0
+// Copyright 2009 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar
5
+
6
+// TODO(dsymonds):
7
+// - catch more errors (no first header, etc.)
8
+
9
+import (
10
+	"bytes"
11
+	"errors"
12
+	"fmt"
13
+	"io"
14
+	"os"
15
+	"path"
16
+	"strconv"
17
+	"strings"
18
+	"time"
19
+)
20
+
21
+var (
22
+	ErrWriteTooLong        = errors.New("archive/tar: write too long")
23
+	ErrFieldTooLong        = errors.New("archive/tar: header field too long")
24
+	ErrWriteAfterClose     = errors.New("archive/tar: write after close")
25
+	errNameTooLong         = errors.New("archive/tar: name too long")
26
+	errFieldTooLongNoAscii = errors.New("archive/tar: header field too long or contains invalid values")
27
+)
28
+
29
+// A Writer provides sequential writing of a tar archive in POSIX.1 format.
30
+// A tar archive consists of a sequence of files.
31
+// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
32
+// writing at most hdr.Size bytes in total.
33
+type Writer struct {
34
+	w          io.Writer
35
+	err        error
36
+	nb         int64 // number of unwritten bytes for current file entry
37
+	pad        int64 // amount of padding to write after current file entry
38
+	closed     bool
39
+	usedBinary bool // whether the binary numeric field extension was used
40
+	preferPax  bool // use pax header instead of binary numeric header
41
+}
42
+
43
+// NewWriter creates a new Writer writing to w.
44
+func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
45
+
46
+// Flush finishes writing the current file (optional).
47
+func (tw *Writer) Flush() error {
48
+	if tw.nb > 0 {
49
+		tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
50
+		return tw.err
51
+	}
52
+
53
+	n := tw.nb + tw.pad
54
+	for n > 0 && tw.err == nil {
55
+		nr := n
56
+		if nr > blockSize {
57
+			nr = blockSize
58
+		}
59
+		var nw int
60
+		nw, tw.err = tw.w.Write(zeroBlock[0:nr])
61
+		n -= int64(nw)
62
+	}
63
+	tw.nb = 0
64
+	tw.pad = 0
65
+	return tw.err
66
+}
67
+
68
+// Write s into b, terminating it with a NUL if there is room.
69
+func (tw *Writer) cString(b []byte, s string) {
70
+	if len(s) > len(b) {
71
+		if tw.err == nil {
72
+			tw.err = ErrFieldTooLong
73
+		}
74
+		return
75
+	}
76
+	copy(b, s)
77
+	if len(s) < len(b) {
78
+		b[len(s)] = 0
79
+	}
80
+}
81
+
82
+// Write s into b, terminating it with a NUL if there is room. If the value is too long for the field add a paxheader record instead
83
+func (tw *Writer) fillHeaderField(b []byte, paxHeader map[string]string, paxKeyword string, s string) {
84
+	needsPaxHeader := len(s) > len(b) || !isASCII7Bit(s)
85
+	if needsPaxHeader {
86
+		paxHeader[paxKeyword] = s
87
+		return
88
+	}
89
+	copy(b, stripTo7BitsAndShorten(s, len(b)))
90
+	if len(s) < len(b) {
91
+		b[len(s)] = 0
92
+	}
93
+}
94
+
95
+// Encode x as an octal ASCII string and write it into b with leading zeros.
96
+func (tw *Writer) octal(b []byte, x int64) {
97
+	s := strconv.FormatInt(x, 8)
98
+	// leading zeros, but leave room for a NUL.
99
+	for len(s)+1 < len(b) {
100
+		s = "0" + s
101
+	}
102
+	tw.cString(b, s)
103
+}
104
+
105
+// Write x into b, either as octal or as binary (GNUtar/star extension).
106
+func (tw *Writer) numeric(b []byte, x int64) {
107
+	// Try octal first.
108
+	s := strconv.FormatInt(x, 8)
109
+	if len(s) < len(b) {
110
+		tw.octal(b, x)
111
+		return
112
+	}
113
+	// Too big: use binary (big-endian).
114
+	tw.usedBinary = true
115
+	for i := len(b) - 1; x > 0 && i >= 0; i-- {
116
+		b[i] = byte(x)
117
+		x >>= 8
118
+	}
119
+	b[0] |= 0x80 // highest bit indicates binary format
120
+}
121
+
122
+// Write x into b, if it is smaller than 2097151. If the value is too long for the field add a paxheader record instead
123
+func (tw *Writer) fillNumericHeaderField(b []byte, paxHeader map[string]string, paxKeyword string, x int64) {
124
+	if tw.preferPax && x > 2097151 {
125
+		s := strconv.FormatInt(x, 10)
126
+		paxHeader[paxKeyword] = s
127
+		tw.numeric(b, 0)
128
+	} else {
129
+		tw.numeric(b, x)
130
+	}
131
+}
132
+
133
+// Write x into b, if it is smaller than 2097151. If the value is too long for the field add a paxheader record instead
134
+func (tw *Writer) fillNumericLongHeaderField(b []byte, paxHeader map[string]string, paxKeyword string, x int64) {
135
+	if tw.preferPax && x > 8589934591 {
136
+		s := strconv.FormatInt(x, 10)
137
+		paxHeader[paxKeyword] = s
138
+		tw.numeric(b, 0)
139
+	} else {
140
+		tw.numeric(b, x)
141
+	}
142
+}
143
+
144
+var (
145
+	minTime = time.Unix(0, 0)
146
+	// There is room for 11 octal digits (33 bits) of mtime.
147
+	maxTime = minTime.Add((1<<33 - 1) * time.Second)
148
+)
149
+
150
+// WriteHeader writes hdr and prepares to accept the file's contents.
151
+// WriteHeader calls Flush if it is not the first header.
152
+// Calling after a Close will return ErrWriteAfterClose.
153
+func (tw *Writer) WriteHeader(hdr *Header) error {
154
+	return tw.writeHeader(hdr, true)
155
+}
156
+
157
+// WriteHeader writes hdr and prepares to accept the file's contents.
158
+// WriteHeader calls Flush if it is not the first header.
159
+// Calling after a Close will return ErrWriteAfterClose.
160
+//  As this method is called internally by writePax header it allows to
161
+// suppress writing the pax header.
162
+func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
163
+	if tw.closed {
164
+		return ErrWriteAfterClose
165
+	}
166
+	if tw.err == nil {
167
+		tw.Flush()
168
+	}
169
+	if tw.err != nil {
170
+		return tw.err
171
+	}
172
+
173
+	// a map to hold pax header records, if any are needed
174
+	paxHeaderRecords := make(map[string]string)
175
+
176
+	// TODO(shanemhansen): we might want to use PAX headers for
177
+	// subsecond time resolution, but for now let's just capture
178
+	// too long fields or non ascii characters
179
+
180
+	header := make([]byte, blockSize)
181
+	s := slicer(header)
182
+
183
+	// keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
184
+	pathHeaderBytes := s.next(fileNameSize)
185
+
186
+	tw.fillHeaderField(pathHeaderBytes, paxHeaderRecords, PAX_PATH, hdr.Name)
187
+
188
+	// Handle out of range ModTime carefully.
189
+	var modTime int64
190
+	if !hdr.ModTime.Before(minTime) && !hdr.ModTime.After(maxTime) {
191
+		modTime = hdr.ModTime.Unix()
192
+	}
193
+
194
+	tw.octal(s.next(8), hdr.Mode)                                                   // 100:108
195
+	tw.fillNumericHeaderField(s.next(8), paxHeaderRecords, PAX_UID, int64(hdr.Uid)) // 108:116
196
+	tw.fillNumericHeaderField(s.next(8), paxHeaderRecords, PAX_GID, int64(hdr.Gid)) // 116:124
197
+	tw.fillNumericLongHeaderField(s.next(12), paxHeaderRecords, PAX_SIZE, hdr.Size) // 124:136
198
+	tw.numeric(s.next(12), modTime)                                                 // 136:148 --- consider using pax for finer granularity
199
+	s.next(8)                                                                       // chksum (148:156)
200
+	s.next(1)[0] = hdr.Typeflag                                                     // 156:157
201
+
202
+	tw.fillHeaderField(s.next(100), paxHeaderRecords, PAX_LINKPATH, hdr.Linkname)
203
+
204
+	copy(s.next(8), []byte("ustar\x0000"))                                 // 257:265
205
+	tw.fillHeaderField(s.next(32), paxHeaderRecords, PAX_UNAME, hdr.Uname) // 265:297
206
+	tw.fillHeaderField(s.next(32), paxHeaderRecords, PAX_GNAME, hdr.Gname) // 297:329
207
+	tw.numeric(s.next(8), hdr.Devmajor)                                    // 329:337
208
+	tw.numeric(s.next(8), hdr.Devminor)                                    // 337:345
209
+
210
+	// keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
211
+	prefixHeaderBytes := s.next(155)
212
+	tw.cString(prefixHeaderBytes, "") // 345:500  prefix
213
+
214
+	// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
215
+	if tw.usedBinary {
216
+		copy(header[257:265], []byte("ustar  \x00"))
217
+	}
218
+
219
+	_, paxPathUsed := paxHeaderRecords[PAX_PATH]
220
+	// try to use a ustar header when only the name is too long
221
+	if !tw.preferPax && len(paxHeaderRecords) == 1 && paxPathUsed {
222
+		suffix := hdr.Name
223
+		prefix := ""
224
+		if len(hdr.Name) > fileNameSize && isASCII7Bit(hdr.Name) {
225
+			var err error
226
+			prefix, suffix, err = tw.splitUSTARLongName(hdr.Name)
227
+			if err == nil {
228
+				// ok we can use a ustar long name instead of pax, now correct the fields
229
+
230
+				// remove the path field from the pax header. this will suppress the pax header
231
+				delete(paxHeaderRecords, PAX_PATH)
232
+
233
+				// update the path fields
234
+				tw.cString(pathHeaderBytes, suffix)
235
+				tw.cString(prefixHeaderBytes, prefix)
236
+
237
+				// Use the ustar magic if we used ustar long names.
238
+				if len(prefix) > 0 {
239
+					copy(header[257:265], []byte("ustar\000"))
240
+				}
241
+			}
242
+		}
243
+	}
244
+
245
+	// The chksum field is terminated by a NUL and a space.
246
+	// This is different from the other octal fields.
247
+	chksum, _ := checksum(header)
248
+	tw.octal(header[148:155], chksum)
249
+	header[155] = ' '
250
+
251
+	if tw.err != nil {
252
+		// problem with header; probably integer too big for a field.
253
+		return tw.err
254
+	}
255
+
256
+	if len(paxHeaderRecords) > 0 {
257
+		if allowPax {
258
+			if err := tw.writePAXHeader(hdr, paxHeaderRecords); err != nil {
259
+				return err
260
+			}
261
+		} else {
262
+			return errFieldTooLongNoAscii
263
+		}
264
+	}
265
+	tw.nb = int64(hdr.Size)
266
+	tw.pad = -tw.nb & (blockSize - 1) // blockSize is a power of two
267
+
268
+	_, tw.err = tw.w.Write(header)
269
+	return tw.err
270
+}
271
+
272
+// writeUSTARLongName splits a USTAR long name hdr.Name.
273
+// name must be < 256 characters. errNameTooLong is returned
274
+// if hdr.Name can't be split. The splitting heuristic
275
+// is compatible with gnu tar.
276
+func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err error) {
277
+	length := len(name)
278
+	if length > fileNamePrefixSize+1 {
279
+		length = fileNamePrefixSize + 1
280
+	} else if name[length-1] == '/' {
281
+		length--
282
+	}
283
+	i := strings.LastIndex(name[:length], "/")
284
+	nlen := length - i - 1
285
+	if i <= 0 || nlen > fileNameSize || nlen == 0 {
286
+		err = errNameTooLong
287
+		return
288
+	}
289
+	prefix, suffix = name[:i], name[i+1:]
290
+	return
291
+}
292
+
293
+// writePaxHeader writes an extended pax header to the
294
+// archive.
295
+func (tw *Writer) writePAXHeader(hdr *Header, paxHeaderRecords map[string]string) error {
296
+	// Prepare extended header
297
+	ext := new(Header)
298
+	ext.Typeflag = TypeXHeader
299
+	// Setting ModTime is required for reader parsing to
300
+	// succeed, and seems harmless enough.
301
+	ext.ModTime = hdr.ModTime
302
+	// The spec asks that we namespace our pseudo files
303
+	// with the current pid.
304
+	pid := os.Getpid()
305
+	dir, file := path.Split(hdr.Name)
306
+	fullName := path.Join(dir,
307
+		fmt.Sprintf("PaxHeaders.%d", pid), file)
308
+
309
+	ext.Name = stripTo7BitsAndShorten(fullName, 100)
310
+	// Construct the body
311
+	var buf bytes.Buffer
312
+
313
+	for k, v := range paxHeaderRecords {
314
+		fmt.Fprint(&buf, paxHeader(k, v))
315
+	}
316
+
317
+	ext.Size = int64(len(buf.Bytes()))
318
+	if err := tw.writeHeader(ext, false); err != nil {
319
+		return err
320
+	}
321
+	if _, err := tw.Write(buf.Bytes()); err != nil {
322
+		return err
323
+	}
324
+	if err := tw.Flush(); err != nil {
325
+		return err
326
+	}
327
+	return nil
328
+}
329
+
330
+// paxHeader formats a single pax record, prefixing it with the appropriate length
331
+func paxHeader(keyword string, value string) string {
332
+
333
+	const padding = 3 // Extra padding for space and newline
334
+	size := len(keyword) + len(value) + padding
335
+	size += len(strconv.Itoa(size))
336
+	record := fmt.Sprintf("%d %s=%s\n", size, keyword, value)
337
+	if len(record) != size {
338
+		// Final adjustment if adding size increased
339
+		// the number of digits in size
340
+		size = len(record)
341
+		record = fmt.Sprintf("%d %s=%s\n", size, keyword, value)
342
+	}
343
+	return record
344
+}
345
+
346
+// Write writes to the current entry in the tar archive.
347
+// Write returns the error ErrWriteTooLong if more than
348
+// hdr.Size bytes are written after WriteHeader.
349
+func (tw *Writer) Write(b []byte) (n int, err error) {
350
+	if tw.closed {
351
+		err = ErrWriteTooLong
352
+		return
353
+	}
354
+	overwrite := false
355
+	if int64(len(b)) > tw.nb {
356
+		b = b[0:tw.nb]
357
+		overwrite = true
358
+	}
359
+	n, err = tw.w.Write(b)
360
+	tw.nb -= int64(n)
361
+	if err == nil && overwrite {
362
+		err = ErrWriteTooLong
363
+		return
364
+	}
365
+	tw.err = err
366
+	return
367
+}
368
+
369
+// Close closes the tar archive, flushing any unwritten
370
+// data to the underlying writer.
371
+func (tw *Writer) Close() error {
372
+	if tw.err != nil || tw.closed {
373
+		return tw.err
374
+	}
375
+	tw.Flush()
376
+	tw.closed = true
377
+	if tw.err != nil {
378
+		return tw.err
379
+	}
380
+
381
+	// trailer: two zero blocks
382
+	for i := 0; i < 2; i++ {
383
+		_, tw.err = tw.w.Write(zeroBlock)
384
+		if tw.err != nil {
385
+			break
386
+		}
387
+	}
388
+	return tw.err
389
+}
0 390
new file mode 100644
... ...
@@ -0,0 +1,357 @@
0
+// Copyright 2009 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package tar
5
+
6
+import (
7
+	"bytes"
8
+	"fmt"
9
+	"io"
10
+	"io/ioutil"
11
+	"os"
12
+	"strings"
13
+	"testing"
14
+	"testing/iotest"
15
+	"time"
16
+)
17
+
18
+type writerTestEntry struct {
19
+	header   *Header
20
+	contents string
21
+}
22
+
23
+type writerTest struct {
24
+	file    string // filename of expected output
25
+	entries []*writerTestEntry
26
+}
27
+
28
+var writerTests = []*writerTest{
29
+	// The writer test file was produced with this command:
30
+	// tar (GNU tar) 1.26
31
+	//   ln -s small.txt link.txt
32
+	//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
33
+	{
34
+		file: "testdata/writer.tar",
35
+		entries: []*writerTestEntry{
36
+			{
37
+				header: &Header{
38
+					Name:     "small.txt",
39
+					Mode:     0640,
40
+					Uid:      73025,
41
+					Gid:      5000,
42
+					Size:     5,
43
+					ModTime:  time.Unix(1246508266, 0),
44
+					Typeflag: '0',
45
+					Uname:    "dsymonds",
46
+					Gname:    "eng",
47
+				},
48
+				contents: "Kilts",
49
+			},
50
+			{
51
+				header: &Header{
52
+					Name:     "small2.txt",
53
+					Mode:     0640,
54
+					Uid:      73025,
55
+					Gid:      5000,
56
+					Size:     11,
57
+					ModTime:  time.Unix(1245217492, 0),
58
+					Typeflag: '0',
59
+					Uname:    "dsymonds",
60
+					Gname:    "eng",
61
+				},
62
+				contents: "Google.com\n",
63
+			},
64
+			{
65
+				header: &Header{
66
+					Name:     "link.txt",
67
+					Mode:     0777,
68
+					Uid:      1000,
69
+					Gid:      1000,
70
+					Size:     0,
71
+					ModTime:  time.Unix(1314603082, 0),
72
+					Typeflag: '2',
73
+					Linkname: "small.txt",
74
+					Uname:    "strings",
75
+					Gname:    "strings",
76
+				},
77
+				// no contents
78
+			},
79
+		},
80
+	},
81
+	// The truncated test file was produced using these commands:
82
+	//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
83
+	//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
84
+	{
85
+		file: "testdata/writer-big.tar",
86
+		entries: []*writerTestEntry{
87
+			{
88
+				header: &Header{
89
+					Name:     "tmp/16gig.txt",
90
+					Mode:     0640,
91
+					Uid:      73025,
92
+					Gid:      5000,
93
+					Size:     16 << 30,
94
+					ModTime:  time.Unix(1254699560, 0),
95
+					Typeflag: '0',
96
+					Uname:    "dsymonds",
97
+					Gname:    "eng",
98
+				},
99
+				// fake contents
100
+				contents: strings.Repeat("\x00", 4<<10),
101
+			},
102
+		},
103
+	},
104
+	// This file was produced using gnu tar 1.17
105
+	// gnutar  -b 4 --format=ustar (longname/)*15 + file.txt
106
+	{
107
+		file: "testdata/ustar.tar",
108
+		entries: []*writerTestEntry{
109
+			{
110
+				header: &Header{
111
+					Name:     strings.Repeat("longname/", 15) + "file.txt",
112
+					Mode:     0644,
113
+					Uid:      0765,
114
+					Gid:      024,
115
+					Size:     06,
116
+					ModTime:  time.Unix(1360135598, 0),
117
+					Typeflag: '0',
118
+					Uname:    "shane",
119
+					Gname:    "staff",
120
+				},
121
+				contents: "hello\n",
122
+			},
123
+		},
124
+	},
125
+}
126
+
127
+// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
128
+func bytestr(offset int, b []byte) string {
129
+	const rowLen = 32
130
+	s := fmt.Sprintf("%04x ", offset)
131
+	for _, ch := range b {
132
+		switch {
133
+		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
134
+			s += fmt.Sprintf("  %c", ch)
135
+		default:
136
+			s += fmt.Sprintf(" %02x", ch)
137
+		}
138
+	}
139
+	return s
140
+}
141
+
142
+// Render a pseudo-diff between two blocks of bytes.
143
+func bytediff(a []byte, b []byte) string {
144
+	const rowLen = 32
145
+	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
146
+	for offset := 0; len(a)+len(b) > 0; offset += rowLen {
147
+		na, nb := rowLen, rowLen
148
+		if na > len(a) {
149
+			na = len(a)
150
+		}
151
+		if nb > len(b) {
152
+			nb = len(b)
153
+		}
154
+		sa := bytestr(offset, a[0:na])
155
+		sb := bytestr(offset, b[0:nb])
156
+		if sa != sb {
157
+			s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
158
+		}
159
+		a = a[na:]
160
+		b = b[nb:]
161
+	}
162
+	return s
163
+}
164
+
165
+func TestWriter(t *testing.T) {
166
+testLoop:
167
+	for i, test := range writerTests {
168
+		expected, err := ioutil.ReadFile(test.file)
169
+		if err != nil {
170
+			t.Errorf("test %d: Unexpected error: %v", i, err)
171
+			continue
172
+		}
173
+
174
+		buf := new(bytes.Buffer)
175
+		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
176
+		big := false
177
+		for j, entry := range test.entries {
178
+			big = big || entry.header.Size > 1<<10
179
+			if err := tw.WriteHeader(entry.header); err != nil {
180
+				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
181
+				continue testLoop
182
+			}
183
+			if _, err := io.WriteString(tw, entry.contents); err != nil {
184
+				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
185
+				continue testLoop
186
+			}
187
+		}
188
+		// Only interested in Close failures for the small tests.
189
+		if err := tw.Close(); err != nil && !big {
190
+			t.Errorf("test %d: Failed closing archive: %v", i, err)
191
+			continue testLoop
192
+		}
193
+
194
+		actual := buf.Bytes()
195
+		if !bytes.Equal(expected, actual) {
196
+			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
197
+				i, bytediff(expected, actual))
198
+		}
199
+		if testing.Short() { // The second test is expensive.
200
+			break
201
+		}
202
+	}
203
+}
204
+
205
+func TestPax(t *testing.T) {
206
+	// Create an archive with a large name
207
+	fileinfo, err := os.Stat("testdata/small.txt")
208
+	if err != nil {
209
+		t.Fatal(err)
210
+	}
211
+	hdr, err := FileInfoHeader(fileinfo, "")
212
+	if err != nil {
213
+		t.Fatalf("os.Stat: %v", err)
214
+	}
215
+	// Force a PAX long name to be written
216
+	longName := strings.Repeat("ab", 100)
217
+	contents := strings.Repeat(" ", int(hdr.Size))
218
+	hdr.Name = longName
219
+	var buf bytes.Buffer
220
+	writer := NewWriter(&buf)
221
+	if err := writer.WriteHeader(hdr); err != nil {
222
+		t.Fatal(err)
223
+	}
224
+	if _, err = writer.Write([]byte(contents)); err != nil {
225
+		t.Fatal(err)
226
+	}
227
+	if err := writer.Close(); err != nil {
228
+		t.Fatal(err)
229
+	}
230
+	// Simple test to make sure PAX extensions are in effect
231
+	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
232
+		t.Fatal("Expected at least one PAX header to be written.")
233
+	}
234
+	// Test that we can get a long name back out of the archive.
235
+	reader := NewReader(&buf)
236
+	hdr, err = reader.Next()
237
+	if err != nil {
238
+		t.Fatal(err)
239
+	}
240
+	if hdr.Name != longName {
241
+		t.Fatal("Couldn't recover long file name")
242
+	}
243
+}
244
+
245
+func TestPaxSymlink(t *testing.T) {
246
+	// Create an archive with a large linkname
247
+	fileinfo, err := os.Stat("testdata/small.txt")
248
+	if err != nil {
249
+		t.Fatal(err)
250
+	}
251
+	hdr, err := FileInfoHeader(fileinfo, "")
252
+	hdr.Typeflag = TypeSymlink
253
+	if err != nil {
254
+		t.Fatalf("os.Stat:1 %v", err)
255
+	}
256
+	// Force a PAX long linkname to be written
257
+	longLinkname := strings.Repeat("1234567890/1234567890", 10)
258
+	hdr.Linkname = longLinkname
259
+
260
+	hdr.Size = 0
261
+	var buf bytes.Buffer
262
+	writer := NewWriter(&buf)
263
+	if err := writer.WriteHeader(hdr); err != nil {
264
+		t.Fatal(err)
265
+	}
266
+	if err := writer.Close(); err != nil {
267
+		t.Fatal(err)
268
+	}
269
+	// Simple test to make sure PAX extensions are in effect
270
+	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
271
+		t.Fatal("Expected at least one PAX header to be written.")
272
+	}
273
+	// Test that we can get a long name back out of the archive.
274
+	reader := NewReader(&buf)
275
+	hdr, err = reader.Next()
276
+	if err != nil {
277
+		t.Fatal(err)
278
+	}
279
+	if hdr.Linkname != longLinkname {
280
+		t.Fatal("Couldn't recover long link name")
281
+	}
282
+}
283
+
284
+func TestPaxNonAscii(t *testing.T) {
285
+	// Create an archive with non ascii. These should trigger a pax header
286
+	// because pax headers have a defined utf-8 encoding.
287
+	fileinfo, err := os.Stat("testdata/small.txt")
288
+	if err != nil {
289
+		t.Fatal(err)
290
+	}
291
+
292
+	hdr, err := FileInfoHeader(fileinfo, "")
293
+	if err != nil {
294
+		t.Fatalf("os.Stat:1 %v", err)
295
+	}
296
+
297
+	// some sample data
298
+	chineseFilename := "文件名"
299
+	chineseGroupname := "組"
300
+	chineseUsername := "用戶名"
301
+
302
+	hdr.Name = chineseFilename
303
+	hdr.Gname = chineseGroupname
304
+	hdr.Uname = chineseUsername
305
+
306
+	contents := strings.Repeat(" ", int(hdr.Size))
307
+
308
+	var buf bytes.Buffer
309
+	writer := NewWriter(&buf)
310
+	if err := writer.WriteHeader(hdr); err != nil {
311
+		t.Fatal(err)
312
+	}
313
+	if _, err = writer.Write([]byte(contents)); err != nil {
314
+		t.Fatal(err)
315
+	}
316
+	if err := writer.Close(); err != nil {
317
+		t.Fatal(err)
318
+	}
319
+	// Simple test to make sure PAX extensions are in effect
320
+	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
321
+		t.Fatal("Expected at least one PAX header to be written.")
322
+	}
323
+	// Test that we can get a long name back out of the archive.
324
+	reader := NewReader(&buf)
325
+	hdr, err = reader.Next()
326
+	if err != nil {
327
+		t.Fatal(err)
328
+	}
329
+	if hdr.Name != chineseFilename {
330
+		t.Fatal("Couldn't recover unicode name")
331
+	}
332
+	if hdr.Gname != chineseGroupname {
333
+		t.Fatal("Couldn't recover unicode group")
334
+	}
335
+	if hdr.Uname != chineseUsername {
336
+		t.Fatal("Couldn't recover unicode user")
337
+	}
338
+}
339
+
340
+func TestPAXHeader(t *testing.T) {
341
+	medName := strings.Repeat("CD", 50)
342
+	longName := strings.Repeat("AB", 100)
343
+	paxTests := [][3]string{
344
+		{PAX_PATH, "/etc/hosts", "19 path=/etc/hosts\n"},
345
+		{"a", "b", "6 a=b\n"},          // Single digit length
346
+		{"a", "names", "11 a=names\n"}, // Test case involving carries
347
+		{PAX_PATH, longName, fmt.Sprintf("210 path=%s\n", longName)},
348
+		{PAX_PATH, medName, fmt.Sprintf("110 path=%s\n", medName)}}
349
+
350
+	for _, test := range paxTests {
351
+		field, key, expected := test[0], test[1], test[2]
352
+		if result := paxHeader(field, key); result != expected {
353
+			t.Fatalf("paxHeader: got %s, expected %s", result, expected)
354
+		}
355
+	}
356
+}
0 357
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
1
+
2
+Redistribution and use in source and binary forms, with or without
3
+modification, are permitted provided that the following conditions are
4
+met:
5
+
6
+	 * Redistributions of source code must retain the above copyright
7
+notice, this list of conditions and the following disclaimer.
8
+	 * Redistributions in binary form must reproduce the above
9
+copyright notice, this list of conditions and the following disclaimer
10
+in the documentation and/or other materials provided with the
11
+distribution.
12
+	 * Neither the name of Google Inc. nor the names of its
13
+contributors may be used to endorse or promote products derived from
14
+this software without specific prior written permission.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 27
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+context
1
+=======
2
+
3
+gorilla/context is a general purpose registry for global request variables.
4
+
5
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
0 6
new file mode 100644
... ...
@@ -0,0 +1,112 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package context
5
+
6
+import (
7
+	"net/http"
8
+	"sync"
9
+	"time"
10
+)
11
+
12
+var (
13
+	mutex sync.Mutex
14
+	data  = make(map[*http.Request]map[interface{}]interface{})
15
+	datat = make(map[*http.Request]int64)
16
+)
17
+
18
+// Set stores a value for a given key in a given request.
19
+func Set(r *http.Request, key, val interface{}) {
20
+	mutex.Lock()
21
+	defer mutex.Unlock()
22
+	if data[r] == nil {
23
+		data[r] = make(map[interface{}]interface{})
24
+		datat[r] = time.Now().Unix()
25
+	}
26
+	data[r][key] = val
27
+}
28
+
29
+// Get returns a value stored for a given key in a given request.
30
+func Get(r *http.Request, key interface{}) interface{} {
31
+	mutex.Lock()
32
+	defer mutex.Unlock()
33
+	if data[r] != nil {
34
+		return data[r][key]
35
+	}
36
+	return nil
37
+}
38
+
39
+// GetOk returns stored value and presence state like multi-value return of map access.
40
+func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
41
+	mutex.Lock()
42
+	defer mutex.Unlock()
43
+	if _, ok := data[r]; ok {
44
+		value, ok := data[r][key]
45
+		return value, ok
46
+	}
47
+	return nil, false
48
+}
49
+
50
+// Delete removes a value stored for a given key in a given request.
51
+func Delete(r *http.Request, key interface{}) {
52
+	mutex.Lock()
53
+	defer mutex.Unlock()
54
+	if data[r] != nil {
55
+		delete(data[r], key)
56
+	}
57
+}
58
+
59
+// Clear removes all values stored for a given request.
60
+//
61
+// This is usually called by a handler wrapper to clean up request
62
+// variables at the end of a request lifetime. See ClearHandler().
63
+func Clear(r *http.Request) {
64
+	mutex.Lock()
65
+	defer mutex.Unlock()
66
+	clear(r)
67
+}
68
+
69
+// clear is Clear without the lock.
70
+func clear(r *http.Request) {
71
+	delete(data, r)
72
+	delete(datat, r)
73
+}
74
+
75
+// Purge removes request data stored for longer than maxAge, in seconds.
76
+// It returns the amount of requests removed.
77
+//
78
+// If maxAge <= 0, all request data is removed.
79
+//
80
+// This is only used for sanity check: in case context cleaning was not
81
+// properly set some request data can be kept forever, consuming an increasing
82
+// amount of memory. In case this is detected, Purge() must be called
83
+// periodically until the problem is fixed.
84
+func Purge(maxAge int) int {
85
+	mutex.Lock()
86
+	defer mutex.Unlock()
87
+	count := 0
88
+	if maxAge <= 0 {
89
+		count = len(data)
90
+		data = make(map[*http.Request]map[interface{}]interface{})
91
+		datat = make(map[*http.Request]int64)
92
+	} else {
93
+		min := time.Now().Unix() - int64(maxAge)
94
+		for r, _ := range data {
95
+			if datat[r] < min {
96
+				clear(r)
97
+				count++
98
+			}
99
+		}
100
+	}
101
+	return count
102
+}
103
+
104
+// ClearHandler wraps an http.Handler and clears request values at the end
105
+// of a request lifetime.
106
+func ClearHandler(h http.Handler) http.Handler {
107
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
108
+		defer Clear(r)
109
+		h.ServeHTTP(w, r)
110
+	})
111
+}
0 112
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package context
5
+
6
+import (
7
+	"net/http"
8
+	"testing"
9
+)
10
+
11
+type keyType int
12
+
13
+const (
14
+	key1 keyType = iota
15
+	key2
16
+)
17
+
18
+func TestContext(t *testing.T) {
19
+	assertEqual := func(val interface{}, exp interface{}) {
20
+		if val != exp {
21
+			t.Errorf("Expected %v, got %v.", exp, val)
22
+		}
23
+	}
24
+
25
+	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
26
+
27
+	// Get()
28
+	assertEqual(Get(r, key1), nil)
29
+
30
+	// Set()
31
+	Set(r, key1, "1")
32
+	assertEqual(Get(r, key1), "1")
33
+	assertEqual(len(data[r]), 1)
34
+
35
+	Set(r, key2, "2")
36
+	assertEqual(Get(r, key2), "2")
37
+	assertEqual(len(data[r]), 2)
38
+
39
+	//GetOk
40
+	value, ok := GetOk(r, key1)
41
+	assertEqual(value, "1")
42
+	assertEqual(ok, true)
43
+
44
+	value, ok = GetOk(r, "not exists")
45
+	assertEqual(value, nil)
46
+	assertEqual(ok, false)
47
+
48
+	Set(r, "nil value", nil)
49
+	value, ok = GetOk(r, "nil value")
50
+	assertEqual(value, nil)
51
+	assertEqual(ok, true)
52
+
53
+	// Delete()
54
+	Delete(r, key1)
55
+	assertEqual(Get(r, key1), nil)
56
+	assertEqual(len(data[r]), 2)
57
+
58
+	Delete(r, key2)
59
+	assertEqual(Get(r, key2), nil)
60
+	assertEqual(len(data[r]), 1)
61
+
62
+	// Clear()
63
+	Clear(r)
64
+	assertEqual(len(data), 0)
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,82 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+/*
5
+Package gorilla/context stores values shared during a request lifetime.
6
+
7
+For example, a router can set variables extracted from the URL and later
8
+application handlers can access those values, or it can be used to store
9
+sessions values to be saved at the end of a request. There are several
10
+others common uses.
11
+
12
+The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
13
+
14
+	http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
15
+
16
+Here's the basic usage: first define the keys that you will need. The key
17
+type is interface{} so a key can be of any type that supports equality.
18
+Here we define a key using a custom int type to avoid name collisions:
19
+
20
+	package foo
21
+
22
+	import (
23
+		"github.com/gorilla/context"
24
+	)
25
+
26
+	type key int
27
+
28
+	const MyKey key = 0
29
+
30
+Then set a variable. Variables are bound to an http.Request object, so you
31
+need a request instance to set a value:
32
+
33
+	context.Set(r, MyKey, "bar")
34
+
35
+The application can later access the variable using the same key you provided:
36
+
37
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
38
+		// val is "bar".
39
+		val := context.Get(r, foo.MyKey)
40
+
41
+		// returns ("bar", true)
42
+		val, ok := context.GetOk(r, foo.MyKey)
43
+		// ...
44
+	}
45
+
46
+And that's all about the basic usage. We discuss some other ideas below.
47
+
48
+Any type can be stored in the context. To enforce a given type, make the key
49
+private and wrap Get() and Set() to accept and return values of a specific
50
+type:
51
+
52
+	type key int
53
+
54
+	const mykey key = 0
55
+
56
+	// GetMyKey returns a value for this package from the request values.
57
+	func GetMyKey(r *http.Request) SomeType {
58
+		if rv := context.Get(r, mykey); rv != nil {
59
+			return rv.(SomeType)
60
+		}
61
+		return nil
62
+	}
63
+
64
+	// SetMyKey sets a value for this package in the request values.
65
+	func SetMyKey(r *http.Request, val SomeType) {
66
+		context.Set(r, mykey, val)
67
+	}
68
+
69
+Variables must be cleared at the end of a request, to remove all values
70
+that were stored. This can be done in an http.Handler, after a request was
71
+served. Just call Clear() passing the request:
72
+
73
+	context.Clear(r)
74
+
75
+...or use ClearHandler(), which conveniently wraps an http.Handler to clear
76
+variables at the end of a request lifetime.
77
+
78
+The Routers from the packages gorilla/mux and gorilla/pat call Clear()
79
+so if you are using either of them you don't need to clear the context manually.
80
+*/
81
+package context
0 82
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
1
+
2
+Redistribution and use in source and binary forms, with or without
3
+modification, are permitted provided that the following conditions are
4
+met:
5
+
6
+	 * Redistributions of source code must retain the above copyright
7
+notice, this list of conditions and the following disclaimer.
8
+	 * Redistributions in binary form must reproduce the above
9
+copyright notice, this list of conditions and the following disclaimer
10
+in the documentation and/or other materials provided with the
11
+distribution.
12
+	 * Neither the name of Google Inc. nor the names of its
13
+contributors may be used to endorse or promote products derived from
14
+this software without specific prior written permission.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 27
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+mux
1
+===
2
+
3
+gorilla/mux is a powerful URL router and dispatcher.
4
+
5
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux
0 6
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package mux
5
+
6
+import (
7
+	"net/http"
8
+	"testing"
9
+)
10
+
11
+func BenchmarkMux(b *testing.B) {
12
+	router := new(Router)
13
+	handler := func(w http.ResponseWriter, r *http.Request) {}
14
+	router.HandleFunc("/v1/{v1}", handler)
15
+
16
+	request, _ := http.NewRequest("GET", "/v1/anything", nil)
17
+	for i := 0; i < b.N; i++ {
18
+		router.ServeHTTP(nil, request)
19
+	}
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,199 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+/*
5
+Package gorilla/mux implements a request router and dispatcher.
6
+
7
+The name mux stands for "HTTP request multiplexer". Like the standard
8
+http.ServeMux, mux.Router matches incoming requests against a list of
9
+registered routes and calls a handler for the route that matches the URL
10
+or other conditions. The main features are:
11
+
12
+	* Requests can be matched based on URL host, path, path prefix, schemes,
13
+	  header and query values, HTTP methods or using custom matchers.
14
+	* URL hosts and paths can have variables with an optional regular
15
+	  expression.
16
+	* Registered URLs can be built, or "reversed", which helps maintaining
17
+	  references to resources.
18
+	* Routes can be used as subrouters: nested routes are only tested if the
19
+	  parent route matches. This is useful to define groups of routes that
20
+	  share common conditions like a host, a path prefix or other repeated
21
+	  attributes. As a bonus, this optimizes request matching.
22
+	* It implements the http.Handler interface so it is compatible with the
23
+	  standard http.ServeMux.
24
+
25
+Let's start registering a couple of URL paths and handlers:
26
+
27
+	func main() {
28
+		r := mux.NewRouter()
29
+		r.HandleFunc("/", HomeHandler)
30
+		r.HandleFunc("/products", ProductsHandler)
31
+		r.HandleFunc("/articles", ArticlesHandler)
32
+		http.Handle("/", r)
33
+	}
34
+
35
+Here we register three routes mapping URL paths to handlers. This is
36
+equivalent to how http.HandleFunc() works: if an incoming request URL matches
37
+one of the paths, the corresponding handler is called passing
38
+(http.ResponseWriter, *http.Request) as parameters.
39
+
40
+Paths can have variables. They are defined using the format {name} or
41
+{name:pattern}. If a regular expression pattern is not defined, the matched
42
+variable will be anything until the next slash. For example:
43
+
44
+	r := mux.NewRouter()
45
+	r.HandleFunc("/products/{key}", ProductHandler)
46
+	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
47
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
48
+
49
+The names are used to create a map of route variables which can be retrieved
50
+calling mux.Vars():
51
+
52
+	vars := mux.Vars(request)
53
+	category := vars["category"]
54
+
55
+And this is all you need to know about the basic usage. More advanced options
56
+are explained below.
57
+
58
+Routes can also be restricted to a domain or subdomain. Just define a host
59
+pattern to be matched. They can also have variables:
60
+
61
+	r := mux.NewRouter()
62
+	// Only matches if domain is "www.domain.com".
63
+	r.Host("www.domain.com")
64
+	// Matches a dynamic subdomain.
65
+	r.Host("{subdomain:[a-z]+}.domain.com")
66
+
67
+There are several other matchers that can be added. To match path prefixes:
68
+
69
+	r.PathPrefix("/products/")
70
+
71
+...or HTTP methods:
72
+
73
+	r.Methods("GET", "POST")
74
+
75
+...or URL schemes:
76
+
77
+	r.Schemes("https")
78
+
79
+...or header values:
80
+
81
+	r.Headers("X-Requested-With", "XMLHttpRequest")
82
+
83
+...or query values:
84
+
85
+	r.Queries("key", "value")
86
+
87
+...or to use a custom matcher function:
88
+
89
+	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
90
+		return r.ProtoMajor == 0
91
+    })
92
+
93
+...and finally, it is possible to combine several matchers in a single route:
94
+
95
+	r.HandleFunc("/products", ProductsHandler).
96
+	  Host("www.domain.com").
97
+	  Methods("GET").
98
+	  Schemes("http")
99
+
100
+Setting the same matching conditions again and again can be boring, so we have
101
+a way to group several routes that share the same requirements.
102
+We call it "subrouting".
103
+
104
+For example, let's say we have several URLs that should only match when the
105
+host is "www.domain.com". Create a route for that host and get a "subrouter"
106
+from it:
107
+
108
+	r := mux.NewRouter()
109
+	s := r.Host("www.domain.com").Subrouter()
110
+
111
+Then register routes in the subrouter:
112
+
113
+	s.HandleFunc("/products/", ProductsHandler)
114
+	s.HandleFunc("/products/{key}", ProductHandler)
115
+	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
116
+
117
+The three URL paths we registered above will only be tested if the domain is
118
+"www.domain.com", because the subrouter is tested first. This is not
119
+only convenient, but also optimizes request matching. You can create
120
+subrouters combining any attribute matchers accepted by a route.
121
+
122
+Subrouters can be used to create domain or path "namespaces": you define
123
+subrouters in a central place and then parts of the app can register its
124
+paths relatively to a given subrouter.
125
+
126
+There's one more thing about subroutes. When a subrouter has a path prefix,
127
+the inner routes use it as base for their paths:
128
+
129
+	r := mux.NewRouter()
130
+	s := r.PathPrefix("/products").Subrouter()
131
+	// "/products/"
132
+	s.HandleFunc("/", ProductsHandler)
133
+	// "/products/{key}/"
134
+	s.HandleFunc("/{key}/", ProductHandler)
135
+	// "/products/{key}/details"
136
+	s.HandleFunc("/{key}/details"), ProductDetailsHandler)
137
+
138
+Now let's see how to build registered URLs.
139
+
140
+Routes can be named. All routes that define a name can have their URLs built,
141
+or "reversed". We define a name calling Name() on a route. For example:
142
+
143
+	r := mux.NewRouter()
144
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
145
+	  Name("article")
146
+
147
+To build a URL, get the route and call the URL() method, passing a sequence of
148
+key/value pairs for the route variables. For the previous route, we would do:
149
+
150
+	url, err := r.Get("article").URL("category", "technology", "id", "42")
151
+
152
+...and the result will be a url.URL with the following path:
153
+
154
+	"/articles/technology/42"
155
+
156
+This also works for host variables:
157
+
158
+	r := mux.NewRouter()
159
+	r.Host("{subdomain}.domain.com").
160
+	  Path("/articles/{category}/{id:[0-9]+}").
161
+	  HandlerFunc(ArticleHandler).
162
+	  Name("article")
163
+
164
+	// url.String() will be "http://news.domain.com/articles/technology/42"
165
+	url, err := r.Get("article").URL("subdomain", "news",
166
+									 "category", "technology",
167
+									 "id", "42")
168
+
169
+All variables defined in the route are required, and their values must
170
+conform to the corresponding patterns. These requirements guarantee that a
171
+generated URL will always match a registered route -- the only exception is
172
+for explicitly defined "build-only" routes which never match.
173
+
174
+There's also a way to build only the URL host or path for a route:
175
+use the methods URLHost() or URLPath() instead. For the previous route,
176
+we would do:
177
+
178
+	// "http://news.domain.com/"
179
+	host, err := r.Get("article").URLHost("subdomain", "news")
180
+
181
+	// "/articles/technology/42"
182
+	path, err := r.Get("article").URLPath("category", "technology", "id", "42")
183
+
184
+And if you use subrouters, host and path defined separately can be built
185
+as well:
186
+
187
+	r := mux.NewRouter()
188
+	s := r.Host("{subdomain}.domain.com").Subrouter()
189
+	s.Path("/articles/{category}/{id:[0-9]+}").
190
+	  HandlerFunc(ArticleHandler).
191
+	  Name("article")
192
+
193
+	// "http://news.domain.com/articles/technology/42"
194
+	url, err := r.Get("article").URL("subdomain", "news",
195
+									 "category", "technology",
196
+									 "id", "42")
197
+*/
198
+package mux
0 199
new file mode 100644
... ...
@@ -0,0 +1,335 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package mux
5
+
6
+import (
7
+	"fmt"
8
+	"net/http"
9
+	"path"
10
+
11
+	"github.com/gorilla/context"
12
+)
13
+
14
+// NewRouter returns a new router instance.
15
+func NewRouter() *Router {
16
+	return &Router{namedRoutes: make(map[string]*Route)}
17
+}
18
+
19
+// Router registers routes to be matched and dispatches a handler.
20
+//
21
+// It implements the http.Handler interface, so it can be registered to serve
22
+// requests:
23
+//
24
+//     var router = mux.NewRouter()
25
+//
26
+//     func main() {
27
+//         http.Handle("/", router)
28
+//     }
29
+//
30
+// Or, for Google App Engine, register it in a init() function:
31
+//
32
+//     func init() {
33
+//         http.Handle("/", router)
34
+//     }
35
+//
36
+// This will send all incoming requests to the router.
37
+type Router struct {
38
+	// Configurable Handler to be used when no route matches.
39
+	NotFoundHandler http.Handler
40
+	// Parent route, if this is a subrouter.
41
+	parent parentRoute
42
+	// Routes to be matched, in order.
43
+	routes []*Route
44
+	// Routes by name for URL building.
45
+	namedRoutes map[string]*Route
46
+	// See Router.StrictSlash(). This defines the flag for new routes.
47
+	strictSlash bool
48
+}
49
+
50
+// Match matches registered routes against the request.
51
+func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
52
+	for _, route := range r.routes {
53
+		if route.Match(req, match) {
54
+			return true
55
+		}
56
+	}
57
+	return false
58
+}
59
+
60
+// ServeHTTP dispatches the handler registered in the matched route.
61
+//
62
+// When there is a match, the route variables can be retrieved calling
63
+// mux.Vars(request).
64
+func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
65
+	// Clean path to canonical form and redirect.
66
+	if p := cleanPath(req.URL.Path); p != req.URL.Path {
67
+		w.Header().Set("Location", p)
68
+		w.WriteHeader(http.StatusMovedPermanently)
69
+		return
70
+	}
71
+	var match RouteMatch
72
+	var handler http.Handler
73
+	if r.Match(req, &match) {
74
+		handler = match.Handler
75
+		setVars(req, match.Vars)
76
+		setCurrentRoute(req, match.Route)
77
+	}
78
+	if handler == nil {
79
+		if r.NotFoundHandler == nil {
80
+			r.NotFoundHandler = http.NotFoundHandler()
81
+		}
82
+		handler = r.NotFoundHandler
83
+	}
84
+	defer context.Clear(req)
85
+	handler.ServeHTTP(w, req)
86
+}
87
+
88
+// Get returns a route registered with the given name.
89
+func (r *Router) Get(name string) *Route {
90
+	return r.getNamedRoutes()[name]
91
+}
92
+
93
+// GetRoute returns a route registered with the given name. This method
94
+// was renamed to Get() and remains here for backwards compatibility.
95
+func (r *Router) GetRoute(name string) *Route {
96
+	return r.getNamedRoutes()[name]
97
+}
98
+
99
+// StrictSlash defines the slash behavior for new routes.
100
+//
101
+// When true, if the route path is "/path/", accessing "/path" will redirect
102
+// to the former and vice versa.
103
+//
104
+// Special case: when a route sets a path prefix, strict slash is
105
+// automatically set to false for that route because the redirect behavior
106
+// can't be determined for prefixes.
107
+func (r *Router) StrictSlash(value bool) *Router {
108
+	r.strictSlash = value
109
+	return r
110
+}
111
+
112
+// ----------------------------------------------------------------------------
113
+// parentRoute
114
+// ----------------------------------------------------------------------------
115
+
116
+// getNamedRoutes returns the map where named routes are registered.
117
+func (r *Router) getNamedRoutes() map[string]*Route {
118
+	if r.namedRoutes == nil {
119
+		if r.parent != nil {
120
+			r.namedRoutes = r.parent.getNamedRoutes()
121
+		} else {
122
+			r.namedRoutes = make(map[string]*Route)
123
+		}
124
+	}
125
+	return r.namedRoutes
126
+}
127
+
128
+// getRegexpGroup returns regexp definitions from the parent route, if any.
129
+func (r *Router) getRegexpGroup() *routeRegexpGroup {
130
+	if r.parent != nil {
131
+		return r.parent.getRegexpGroup()
132
+	}
133
+	return nil
134
+}
135
+
136
+// ----------------------------------------------------------------------------
137
+// Route factories
138
+// ----------------------------------------------------------------------------
139
+
140
+// NewRoute registers an empty route.
141
+func (r *Router) NewRoute() *Route {
142
+	route := &Route{parent: r, strictSlash: r.strictSlash}
143
+	r.routes = append(r.routes, route)
144
+	return route
145
+}
146
+
147
+// Handle registers a new route with a matcher for the URL path.
148
+// See Route.Path() and Route.Handler().
149
+func (r *Router) Handle(path string, handler http.Handler) *Route {
150
+	return r.NewRoute().Path(path).Handler(handler)
151
+}
152
+
153
+// HandleFunc registers a new route with a matcher for the URL path.
154
+// See Route.Path() and Route.HandlerFunc().
155
+func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
156
+	*http.Request)) *Route {
157
+	return r.NewRoute().Path(path).HandlerFunc(f)
158
+}
159
+
160
+// Headers registers a new route with a matcher for request header values.
161
+// See Route.Headers().
162
+func (r *Router) Headers(pairs ...string) *Route {
163
+	return r.NewRoute().Headers(pairs...)
164
+}
165
+
166
+// Host registers a new route with a matcher for the URL host.
167
+// See Route.Host().
168
+func (r *Router) Host(tpl string) *Route {
169
+	return r.NewRoute().Host(tpl)
170
+}
171
+
172
+// MatcherFunc registers a new route with a custom matcher function.
173
+// See Route.MatcherFunc().
174
+func (r *Router) MatcherFunc(f MatcherFunc) *Route {
175
+	return r.NewRoute().MatcherFunc(f)
176
+}
177
+
178
+// Methods registers a new route with a matcher for HTTP methods.
179
+// See Route.Methods().
180
+func (r *Router) Methods(methods ...string) *Route {
181
+	return r.NewRoute().Methods(methods...)
182
+}
183
+
184
+// Path registers a new route with a matcher for the URL path.
185
+// See Route.Path().
186
+func (r *Router) Path(tpl string) *Route {
187
+	return r.NewRoute().Path(tpl)
188
+}
189
+
190
+// PathPrefix registers a new route with a matcher for the URL path prefix.
191
+// See Route.PathPrefix().
192
+func (r *Router) PathPrefix(tpl string) *Route {
193
+	return r.NewRoute().PathPrefix(tpl)
194
+}
195
+
196
+// Queries registers a new route with a matcher for URL query values.
197
+// See Route.Queries().
198
+func (r *Router) Queries(pairs ...string) *Route {
199
+	return r.NewRoute().Queries(pairs...)
200
+}
201
+
202
+// Schemes registers a new route with a matcher for URL schemes.
203
+// See Route.Schemes().
204
+func (r *Router) Schemes(schemes ...string) *Route {
205
+	return r.NewRoute().Schemes(schemes...)
206
+}
207
+
208
+// ----------------------------------------------------------------------------
209
+// Context
210
+// ----------------------------------------------------------------------------
211
+
212
+// RouteMatch stores information about a matched route.
213
+type RouteMatch struct {
214
+	Route   *Route
215
+	Handler http.Handler
216
+	Vars    map[string]string
217
+}
218
+
219
+type contextKey int
220
+
221
+const (
222
+	varsKey contextKey = iota
223
+	routeKey
224
+)
225
+
226
+// Vars returns the route variables for the current request, if any.
227
+func Vars(r *http.Request) map[string]string {
228
+	if rv := context.Get(r, varsKey); rv != nil {
229
+		return rv.(map[string]string)
230
+	}
231
+	return nil
232
+}
233
+
234
+// CurrentRoute returns the matched route for the current request, if any.
235
+func CurrentRoute(r *http.Request) *Route {
236
+	if rv := context.Get(r, routeKey); rv != nil {
237
+		return rv.(*Route)
238
+	}
239
+	return nil
240
+}
241
+
242
+func setVars(r *http.Request, val interface{}) {
243
+	context.Set(r, varsKey, val)
244
+}
245
+
246
+func setCurrentRoute(r *http.Request, val interface{}) {
247
+	context.Set(r, routeKey, val)
248
+}
249
+
250
+// ----------------------------------------------------------------------------
251
+// Helpers
252
+// ----------------------------------------------------------------------------
253
+
254
+// cleanPath returns the canonical path for p, eliminating . and .. elements.
255
+// Borrowed from the net/http package.
256
+func cleanPath(p string) string {
257
+	if p == "" {
258
+		return "/"
259
+	}
260
+	if p[0] != '/' {
261
+		p = "/" + p
262
+	}
263
+	np := path.Clean(p)
264
+	// path.Clean removes trailing slash except for root;
265
+	// put the trailing slash back if necessary.
266
+	if p[len(p)-1] == '/' && np != "/" {
267
+		np += "/"
268
+	}
269
+	return np
270
+}
271
+
272
+// uniqueVars returns an error if two slices contain duplicated strings.
273
+func uniqueVars(s1, s2 []string) error {
274
+	for _, v1 := range s1 {
275
+		for _, v2 := range s2 {
276
+			if v1 == v2 {
277
+				return fmt.Errorf("mux: duplicated route variable %q", v2)
278
+			}
279
+		}
280
+	}
281
+	return nil
282
+}
283
+
284
+// mapFromPairs converts variadic string parameters to a string map.
285
+func mapFromPairs(pairs ...string) (map[string]string, error) {
286
+	length := len(pairs)
287
+	if length%2 != 0 {
288
+		return nil, fmt.Errorf(
289
+			"mux: number of parameters must be multiple of 2, got %v", pairs)
290
+	}
291
+	m := make(map[string]string, length/2)
292
+	for i := 0; i < length; i += 2 {
293
+		m[pairs[i]] = pairs[i+1]
294
+	}
295
+	return m, nil
296
+}
297
+
298
+// matchInArray returns true if the given string value is in the array.
299
+func matchInArray(arr []string, value string) bool {
300
+	for _, v := range arr {
301
+		if v == value {
302
+			return true
303
+		}
304
+	}
305
+	return false
306
+}
307
+
308
+// matchMap returns true if the given key/value pairs exist in a given map.
309
+func matchMap(toCheck map[string]string, toMatch map[string][]string,
310
+	canonicalKey bool) bool {
311
+	for k, v := range toCheck {
312
+		// Check if key exists.
313
+		if canonicalKey {
314
+			k = http.CanonicalHeaderKey(k)
315
+		}
316
+		if values := toMatch[k]; values == nil {
317
+			return false
318
+		} else if v != "" {
319
+			// If value was defined as an empty string we only check that the
320
+			// key exists. Otherwise we also check for equality.
321
+			valueExists := false
322
+			for _, value := range values {
323
+				if v == value {
324
+					valueExists = true
325
+					break
326
+				}
327
+			}
328
+			if !valueExists {
329
+				return false
330
+			}
331
+		}
332
+	}
333
+	return true
334
+}
0 335
new file mode 100644
... ...
@@ -0,0 +1,723 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package mux
5
+
6
+import (
7
+	"fmt"
8
+	"net/http"
9
+	"testing"
10
+)
11
+
12
+type routeTest struct {
13
+	title       string            // title of the test
14
+	route       *Route            // the route being tested
15
+	request     *http.Request     // a request to test the route
16
+	vars        map[string]string // the expected vars of the match
17
+	host        string            // the expected host of the match
18
+	path        string            // the expected path of the match
19
+	shouldMatch bool              // whether the request is expected to match the route at all
20
+}
21
+
22
+func TestHost(t *testing.T) {
23
+	// newRequestHost a new request with a method, url, and host header
24
+	newRequestHost := func(method, url, host string) *http.Request {
25
+		req, err := http.NewRequest(method, url, nil)
26
+		if err != nil {
27
+			panic(err)
28
+		}
29
+		req.Host = host
30
+		return req
31
+	}
32
+
33
+	tests := []routeTest{
34
+		{
35
+			title:       "Host route match",
36
+			route:       new(Route).Host("aaa.bbb.ccc"),
37
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
38
+			vars:        map[string]string{},
39
+			host:        "aaa.bbb.ccc",
40
+			path:        "",
41
+			shouldMatch: true,
42
+		},
43
+		{
44
+			title:       "Host route, wrong host in request URL",
45
+			route:       new(Route).Host("aaa.bbb.ccc"),
46
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
47
+			vars:        map[string]string{},
48
+			host:        "aaa.bbb.ccc",
49
+			path:        "",
50
+			shouldMatch: false,
51
+		},
52
+		{
53
+			title:       "Host route with port, match",
54
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
55
+			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
56
+			vars:        map[string]string{},
57
+			host:        "aaa.bbb.ccc:1234",
58
+			path:        "",
59
+			shouldMatch: true,
60
+		},
61
+		{
62
+			title:       "Host route with port, wrong port in request URL",
63
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
64
+			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
65
+			vars:        map[string]string{},
66
+			host:        "aaa.bbb.ccc:1234",
67
+			path:        "",
68
+			shouldMatch: false,
69
+		},
70
+		{
71
+			title:       "Host route, match with host in request header",
72
+			route:       new(Route).Host("aaa.bbb.ccc"),
73
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
74
+			vars:        map[string]string{},
75
+			host:        "aaa.bbb.ccc",
76
+			path:        "",
77
+			shouldMatch: true,
78
+		},
79
+		{
80
+			title:       "Host route, wrong host in request header",
81
+			route:       new(Route).Host("aaa.bbb.ccc"),
82
+			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
83
+			vars:        map[string]string{},
84
+			host:        "aaa.bbb.ccc",
85
+			path:        "",
86
+			shouldMatch: false,
87
+		},
88
+		// BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
89
+		{
90
+			title:       "Host route with port, wrong host in request header",
91
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
92
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
93
+			vars:        map[string]string{},
94
+			host:        "aaa.bbb.ccc:1234",
95
+			path:        "",
96
+			shouldMatch: false,
97
+		},
98
+		{
99
+			title:       "Host route with pattern, match",
100
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
101
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
102
+			vars:        map[string]string{"v1": "bbb"},
103
+			host:        "aaa.bbb.ccc",
104
+			path:        "",
105
+			shouldMatch: true,
106
+		},
107
+		{
108
+			title:       "Host route with pattern, wrong host in request URL",
109
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
110
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
111
+			vars:        map[string]string{"v1": "bbb"},
112
+			host:        "aaa.bbb.ccc",
113
+			path:        "",
114
+			shouldMatch: false,
115
+		},
116
+		{
117
+			title:       "Host route with multiple patterns, match",
118
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
119
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
120
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
121
+			host:        "aaa.bbb.ccc",
122
+			path:        "",
123
+			shouldMatch: true,
124
+		},
125
+		{
126
+			title:       "Host route with multiple patterns, wrong host in request URL",
127
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
128
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
129
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
130
+			host:        "aaa.bbb.ccc",
131
+			path:        "",
132
+			shouldMatch: false,
133
+		},
134
+	}
135
+	for _, test := range tests {
136
+		testRoute(t, test)
137
+	}
138
+}
139
+
140
+func TestPath(t *testing.T) {
141
+	tests := []routeTest{
142
+		{
143
+			title:       "Path route, match",
144
+			route:       new(Route).Path("/111/222/333"),
145
+			request:     newRequest("GET", "http://localhost/111/222/333"),
146
+			vars:        map[string]string{},
147
+			host:        "",
148
+			path:        "/111/222/333",
149
+			shouldMatch: true,
150
+		},
151
+		{
152
+			title:       "Path route, wrong path in request in request URL",
153
+			route:       new(Route).Path("/111/222/333"),
154
+			request:     newRequest("GET", "http://localhost/1/2/3"),
155
+			vars:        map[string]string{},
156
+			host:        "",
157
+			path:        "/111/222/333",
158
+			shouldMatch: false,
159
+		},
160
+		{
161
+			title:       "Path route with pattern, match",
162
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
163
+			request:     newRequest("GET", "http://localhost/111/222/333"),
164
+			vars:        map[string]string{"v1": "222"},
165
+			host:        "",
166
+			path:        "/111/222/333",
167
+			shouldMatch: true,
168
+		},
169
+		{
170
+			title:       "Path route with pattern, URL in request does not match",
171
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
172
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
173
+			vars:        map[string]string{"v1": "222"},
174
+			host:        "",
175
+			path:        "/111/222/333",
176
+			shouldMatch: false,
177
+		},
178
+		{
179
+			title:       "Path route with multiple patterns, match",
180
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
181
+			request:     newRequest("GET", "http://localhost/111/222/333"),
182
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
183
+			host:        "",
184
+			path:        "/111/222/333",
185
+			shouldMatch: true,
186
+		},
187
+		{
188
+			title:       "Path route with multiple patterns, URL in request does not match",
189
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
190
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
191
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
192
+			host:        "",
193
+			path:        "/111/222/333",
194
+			shouldMatch: false,
195
+		},
196
+	}
197
+
198
+	for _, test := range tests {
199
+		testRoute(t, test)
200
+	}
201
+}
202
+
203
+func TestPathPrefix(t *testing.T) {
204
+	tests := []routeTest{
205
+		{
206
+			title:       "PathPrefix route, match",
207
+			route:       new(Route).PathPrefix("/111"),
208
+			request:     newRequest("GET", "http://localhost/111/222/333"),
209
+			vars:        map[string]string{},
210
+			host:        "",
211
+			path:        "/111",
212
+			shouldMatch: true,
213
+		},
214
+		{
215
+			title:       "PathPrefix route, URL prefix in request does not match",
216
+			route:       new(Route).PathPrefix("/111"),
217
+			request:     newRequest("GET", "http://localhost/1/2/3"),
218
+			vars:        map[string]string{},
219
+			host:        "",
220
+			path:        "/111",
221
+			shouldMatch: false,
222
+		},
223
+		{
224
+			title:       "PathPrefix route with pattern, match",
225
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
226
+			request:     newRequest("GET", "http://localhost/111/222/333"),
227
+			vars:        map[string]string{"v1": "222"},
228
+			host:        "",
229
+			path:        "/111/222",
230
+			shouldMatch: true,
231
+		},
232
+		{
233
+			title:       "PathPrefix route with pattern, URL prefix in request does not match",
234
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
235
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
236
+			vars:        map[string]string{"v1": "222"},
237
+			host:        "",
238
+			path:        "/111/222",
239
+			shouldMatch: false,
240
+		},
241
+		{
242
+			title:       "PathPrefix route with multiple patterns, match",
243
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
244
+			request:     newRequest("GET", "http://localhost/111/222/333"),
245
+			vars:        map[string]string{"v1": "111", "v2": "222"},
246
+			host:        "",
247
+			path:        "/111/222",
248
+			shouldMatch: true,
249
+		},
250
+		{
251
+			title:       "PathPrefix route with multiple patterns, URL prefix in request does not match",
252
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
253
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
254
+			vars:        map[string]string{"v1": "111", "v2": "222"},
255
+			host:        "",
256
+			path:        "/111/222",
257
+			shouldMatch: false,
258
+		},
259
+	}
260
+
261
+	for _, test := range tests {
262
+		testRoute(t, test)
263
+	}
264
+}
265
+
266
+func TestHostPath(t *testing.T) {
267
+	tests := []routeTest{
268
+		{
269
+			title:       "Host and Path route, match",
270
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
271
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
272
+			vars:        map[string]string{},
273
+			host:        "",
274
+			path:        "",
275
+			shouldMatch: true,
276
+		},
277
+		{
278
+			title:       "Host and Path route, wrong host in request URL",
279
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
280
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
281
+			vars:        map[string]string{},
282
+			host:        "",
283
+			path:        "",
284
+			shouldMatch: false,
285
+		},
286
+		{
287
+			title:       "Host and Path route with pattern, match",
288
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
289
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
290
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
291
+			host:        "aaa.bbb.ccc",
292
+			path:        "/111/222/333",
293
+			shouldMatch: true,
294
+		},
295
+		{
296
+			title:       "Host and Path route with pattern, URL in request does not match",
297
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
298
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
299
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
300
+			host:        "aaa.bbb.ccc",
301
+			path:        "/111/222/333",
302
+			shouldMatch: false,
303
+		},
304
+		{
305
+			title:       "Host and Path route with multiple patterns, match",
306
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
307
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
308
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
309
+			host:        "aaa.bbb.ccc",
310
+			path:        "/111/222/333",
311
+			shouldMatch: true,
312
+		},
313
+		{
314
+			title:       "Host and Path route with multiple patterns, URL in request does not match",
315
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
316
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
317
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
318
+			host:        "aaa.bbb.ccc",
319
+			path:        "/111/222/333",
320
+			shouldMatch: false,
321
+		},
322
+	}
323
+
324
+	for _, test := range tests {
325
+		testRoute(t, test)
326
+	}
327
+}
328
+
329
+func TestHeaders(t *testing.T) {
330
+	// newRequestHeaders creates a new request with a method, url, and headers
331
+	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
332
+		req, err := http.NewRequest(method, url, nil)
333
+		if err != nil {
334
+			panic(err)
335
+		}
336
+		for k, v := range headers {
337
+			req.Header.Add(k, v)
338
+		}
339
+		return req
340
+	}
341
+
342
+	tests := []routeTest{
343
+		{
344
+			title:       "Headers route, match",
345
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
346
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
347
+			vars:        map[string]string{},
348
+			host:        "",
349
+			path:        "",
350
+			shouldMatch: true,
351
+		},
352
+		{
353
+			title:       "Headers route, bad header values",
354
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
355
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
356
+			vars:        map[string]string{},
357
+			host:        "",
358
+			path:        "",
359
+			shouldMatch: false,
360
+		},
361
+	}
362
+
363
+	for _, test := range tests {
364
+		testRoute(t, test)
365
+	}
366
+
367
+}
368
+
369
+func TestMethods(t *testing.T) {
370
+	tests := []routeTest{
371
+		{
372
+			title:       "Methods route, match GET",
373
+			route:       new(Route).Methods("GET", "POST"),
374
+			request:     newRequest("GET", "http://localhost"),
375
+			vars:        map[string]string{},
376
+			host:        "",
377
+			path:        "",
378
+			shouldMatch: true,
379
+		},
380
+		{
381
+			title:       "Methods route, match POST",
382
+			route:       new(Route).Methods("GET", "POST"),
383
+			request:     newRequest("POST", "http://localhost"),
384
+			vars:        map[string]string{},
385
+			host:        "",
386
+			path:        "",
387
+			shouldMatch: true,
388
+		},
389
+		{
390
+			title:       "Methods route, bad method",
391
+			route:       new(Route).Methods("GET", "POST"),
392
+			request:     newRequest("PUT", "http://localhost"),
393
+			vars:        map[string]string{},
394
+			host:        "",
395
+			path:        "",
396
+			shouldMatch: false,
397
+		},
398
+	}
399
+
400
+	for _, test := range tests {
401
+		testRoute(t, test)
402
+	}
403
+}
404
+
405
+func TestQueries(t *testing.T) {
406
+	tests := []routeTest{
407
+		{
408
+			title:       "Queries route, match",
409
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
410
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
411
+			vars:        map[string]string{},
412
+			host:        "",
413
+			path:        "",
414
+			shouldMatch: true,
415
+		},
416
+		{
417
+			title:       "Queries route, bad query",
418
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
419
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=dong"),
420
+			vars:        map[string]string{},
421
+			host:        "",
422
+			path:        "",
423
+			shouldMatch: false,
424
+		},
425
+	}
426
+
427
+	for _, test := range tests {
428
+		testRoute(t, test)
429
+	}
430
+}
431
+
432
+func TestSchemes(t *testing.T) {
433
+	tests := []routeTest{
434
+		// Schemes
435
+		{
436
+			title:       "Schemes route, match https",
437
+			route:       new(Route).Schemes("https", "ftp"),
438
+			request:     newRequest("GET", "https://localhost"),
439
+			vars:        map[string]string{},
440
+			host:        "",
441
+			path:        "",
442
+			shouldMatch: true,
443
+		},
444
+		{
445
+			title:       "Schemes route, match ftp",
446
+			route:       new(Route).Schemes("https", "ftp"),
447
+			request:     newRequest("GET", "ftp://localhost"),
448
+			vars:        map[string]string{},
449
+			host:        "",
450
+			path:        "",
451
+			shouldMatch: true,
452
+		},
453
+		{
454
+			title:       "Schemes route, bad scheme",
455
+			route:       new(Route).Schemes("https", "ftp"),
456
+			request:     newRequest("GET", "http://localhost"),
457
+			vars:        map[string]string{},
458
+			host:        "",
459
+			path:        "",
460
+			shouldMatch: false,
461
+		},
462
+	}
463
+	for _, test := range tests {
464
+		testRoute(t, test)
465
+	}
466
+}
467
+
468
+func TestMatcherFunc(t *testing.T) {
469
+	m := func(r *http.Request, m *RouteMatch) bool {
470
+		if r.URL.Host == "aaa.bbb.ccc" {
471
+			return true
472
+		}
473
+		return false
474
+	}
475
+
476
+	tests := []routeTest{
477
+		{
478
+			title:       "MatchFunc route, match",
479
+			route:       new(Route).MatcherFunc(m),
480
+			request:     newRequest("GET", "http://aaa.bbb.ccc"),
481
+			vars:        map[string]string{},
482
+			host:        "",
483
+			path:        "",
484
+			shouldMatch: true,
485
+		},
486
+		{
487
+			title:       "MatchFunc route, non-match",
488
+			route:       new(Route).MatcherFunc(m),
489
+			request:     newRequest("GET", "http://aaa.222.ccc"),
490
+			vars:        map[string]string{},
491
+			host:        "",
492
+			path:        "",
493
+			shouldMatch: false,
494
+		},
495
+	}
496
+
497
+	for _, test := range tests {
498
+		testRoute(t, test)
499
+	}
500
+}
501
+
502
+func TestSubRouter(t *testing.T) {
503
+	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
504
+	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
505
+
506
+	tests := []routeTest{
507
+		{
508
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
509
+			request:     newRequest("GET", "http://aaa.google.com/bbb"),
510
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
511
+			host:        "aaa.google.com",
512
+			path:        "/bbb",
513
+			shouldMatch: true,
514
+		},
515
+		{
516
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
517
+			request:     newRequest("GET", "http://111.google.com/111"),
518
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
519
+			host:        "aaa.google.com",
520
+			path:        "/bbb",
521
+			shouldMatch: false,
522
+		},
523
+		{
524
+			route:       subrouter2.Path("/baz/{v2}"),
525
+			request:     newRequest("GET", "http://localhost/foo/bar/baz/ding"),
526
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
527
+			host:        "",
528
+			path:        "/foo/bar/baz/ding",
529
+			shouldMatch: true,
530
+		},
531
+		{
532
+			route:       subrouter2.Path("/baz/{v2}"),
533
+			request:     newRequest("GET", "http://localhost/foo/bar"),
534
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
535
+			host:        "",
536
+			path:        "/foo/bar/baz/ding",
537
+			shouldMatch: false,
538
+		},
539
+	}
540
+
541
+	for _, test := range tests {
542
+		testRoute(t, test)
543
+	}
544
+}
545
+
546
+func TestNamedRoutes(t *testing.T) {
547
+	r1 := NewRouter()
548
+	r1.NewRoute().Name("a")
549
+	r1.NewRoute().Name("b")
550
+	r1.NewRoute().Name("c")
551
+
552
+	r2 := r1.NewRoute().Subrouter()
553
+	r2.NewRoute().Name("d")
554
+	r2.NewRoute().Name("e")
555
+	r2.NewRoute().Name("f")
556
+
557
+	r3 := r2.NewRoute().Subrouter()
558
+	r3.NewRoute().Name("g")
559
+	r3.NewRoute().Name("h")
560
+	r3.NewRoute().Name("i")
561
+
562
+	if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
563
+		t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
564
+	} else if r1.Get("i") == nil {
565
+		t.Errorf("Subroute name not registered")
566
+	}
567
+}
568
+
569
+func TestStrictSlash(t *testing.T) {
570
+	var r *Router
571
+	var req *http.Request
572
+	var route *Route
573
+	var match *RouteMatch
574
+	var matched bool
575
+
576
+	// StrictSlash should be ignored for path prefix.
577
+	// So we register a route ending in slash but it doesn't attempt to add
578
+	// the slash for a path not ending in slash.
579
+	r = NewRouter()
580
+	r.StrictSlash(true)
581
+	route = r.NewRoute().PathPrefix("/static/")
582
+	req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil)
583
+	match = new(RouteMatch)
584
+	matched = r.Match(req, match)
585
+	if !matched {
586
+		t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route))
587
+	}
588
+	if match.Handler != nil {
589
+		t.Errorf("Should not redirect")
590
+	}
591
+}
592
+
593
+// ----------------------------------------------------------------------------
594
+// Helpers
595
+// ----------------------------------------------------------------------------
596
+
597
+func getRouteTemplate(route *Route) string {
598
+	host, path := "none", "none"
599
+	if route.regexp != nil {
600
+		if route.regexp.host != nil {
601
+			host = route.regexp.host.template
602
+		}
603
+		if route.regexp.path != nil {
604
+			path = route.regexp.path.template
605
+		}
606
+	}
607
+	return fmt.Sprintf("Host: %v, Path: %v", host, path)
608
+}
609
+
610
+func testRoute(t *testing.T, test routeTest) {
611
+	request := test.request
612
+	route := test.route
613
+	vars := test.vars
614
+	shouldMatch := test.shouldMatch
615
+	host := test.host
616
+	path := test.path
617
+	url := test.host + test.path
618
+
619
+	var match RouteMatch
620
+	ok := route.Match(request, &match)
621
+	if ok != shouldMatch {
622
+		msg := "Should match"
623
+		if !shouldMatch {
624
+			msg = "Should not match"
625
+		}
626
+		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
627
+		return
628
+	}
629
+	if shouldMatch {
630
+		if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
631
+			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
632
+			return
633
+		}
634
+		if host != "" {
635
+			u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
636
+			if host != u.Host {
637
+				t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
638
+				return
639
+			}
640
+		}
641
+		if path != "" {
642
+			u, _ := route.URLPath(mapToPairs(match.Vars)...)
643
+			if path != u.Path {
644
+				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
645
+				return
646
+			}
647
+		}
648
+		if url != "" {
649
+			u, _ := route.URL(mapToPairs(match.Vars)...)
650
+			if url != u.Host+u.Path {
651
+				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
652
+				return
653
+			}
654
+		}
655
+	}
656
+}
657
+
658
+// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
659
+func TestSubrouterHeader(t *testing.T) {
660
+	expected := "func1 response"
661
+	func1 := func(w http.ResponseWriter, r *http.Request) {
662
+		fmt.Fprint(w, expected)
663
+	}
664
+	func2 := func(http.ResponseWriter, *http.Request) {}
665
+
666
+	r := NewRouter()
667
+	s := r.Headers("SomeSpecialHeader", "").Subrouter()
668
+	s.HandleFunc("/", func1).Name("func1")
669
+	r.HandleFunc("/", func2).Name("func2")
670
+
671
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
672
+	req.Header.Add("SomeSpecialHeader", "foo")
673
+	match := new(RouteMatch)
674
+	matched := r.Match(req, match)
675
+	if !matched {
676
+		t.Errorf("Should match request")
677
+	}
678
+	if match.Route.GetName() != "func1" {
679
+		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
680
+	}
681
+	resp := NewRecorder()
682
+	match.Handler.ServeHTTP(resp, req)
683
+	if resp.Body.String() != expected {
684
+		t.Errorf("Expecting %q", expected)
685
+	}
686
+}
687
+
688
+// mapToPairs converts a string map to a slice of string pairs
689
+func mapToPairs(m map[string]string) []string {
690
+	var i int
691
+	p := make([]string, len(m)*2)
692
+	for k, v := range m {
693
+		p[i] = k
694
+		p[i+1] = v
695
+		i += 2
696
+	}
697
+	return p
698
+}
699
+
700
+// stringMapEqual checks the equality of two string maps
701
+func stringMapEqual(m1, m2 map[string]string) bool {
702
+	nil1 := m1 == nil
703
+	nil2 := m2 == nil
704
+	if nil1 != nil2 || len(m1) != len(m2) {
705
+		return false
706
+	}
707
+	for k, v := range m1 {
708
+		if v != m2[k] {
709
+			return false
710
+		}
711
+	}
712
+	return true
713
+}
714
+
715
+// newRequest is a helper function to create a new request with a method and url
716
+func newRequest(method, url string) *http.Request {
717
+	req, err := http.NewRequest(method, url, nil)
718
+	if err != nil {
719
+		panic(err)
720
+	}
721
+	return req
722
+}
0 723
new file mode 100644
... ...
@@ -0,0 +1,758 @@
0
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
1
+
2
+// Copyright 2011 Gorilla Authors. All rights reserved.
3
+// Use of this source code is governed by a BSD-style
4
+// license that can be found in the LICENSE file.
5
+
6
+package mux
7
+
8
+import (
9
+	"bytes"
10
+	"net/http"
11
+	"testing"
12
+)
13
+
14
+// ----------------------------------------------------------------------------
15
+// ResponseRecorder
16
+// ----------------------------------------------------------------------------
17
+// Copyright 2009 The Go Authors. All rights reserved.
18
+// Use of this source code is governed by a BSD-style
19
+// license that can be found in the LICENSE file.
20
+
21
+// ResponseRecorder is an implementation of http.ResponseWriter that
22
+// records its mutations for later inspection in tests.
23
+type ResponseRecorder struct {
24
+	Code      int           // the HTTP response code from WriteHeader
25
+	HeaderMap http.Header   // the HTTP response headers
26
+	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
27
+	Flushed   bool
28
+}
29
+
30
+// NewRecorder returns an initialized ResponseRecorder.
31
+func NewRecorder() *ResponseRecorder {
32
+	return &ResponseRecorder{
33
+		HeaderMap: make(http.Header),
34
+		Body:      new(bytes.Buffer),
35
+	}
36
+}
37
+
38
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
39
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
40
+const DefaultRemoteAddr = "1.2.3.4"
41
+
42
+// Header returns the response headers.
43
+func (rw *ResponseRecorder) Header() http.Header {
44
+	return rw.HeaderMap
45
+}
46
+
47
+// Write always succeeds and writes to rw.Body, if not nil.
48
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
49
+	if rw.Body != nil {
50
+		rw.Body.Write(buf)
51
+	}
52
+	if rw.Code == 0 {
53
+		rw.Code = http.StatusOK
54
+	}
55
+	return len(buf), nil
56
+}
57
+
58
+// WriteHeader sets rw.Code.
59
+func (rw *ResponseRecorder) WriteHeader(code int) {
60
+	rw.Code = code
61
+}
62
+
63
+// Flush sets rw.Flushed to true.
64
+func (rw *ResponseRecorder) Flush() {
65
+	rw.Flushed = true
66
+}
67
+
68
+// ----------------------------------------------------------------------------
69
+
70
+func TestRouteMatchers(t *testing.T) {
71
+	var scheme, host, path, query, method string
72
+	var headers map[string]string
73
+	var resultVars map[bool]map[string]string
74
+
75
+	router := NewRouter()
76
+	router.NewRoute().Host("{var1}.google.com").
77
+		Path("/{var2:[a-z]+}/{var3:[0-9]+}").
78
+		Queries("foo", "bar").
79
+		Methods("GET").
80
+		Schemes("https").
81
+		Headers("x-requested-with", "XMLHttpRequest")
82
+	router.NewRoute().Host("www.{var4}.com").
83
+		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
84
+		Queries("baz", "ding").
85
+		Methods("POST").
86
+		Schemes("http").
87
+		Headers("Content-Type", "application/json")
88
+
89
+	reset := func() {
90
+		// Everything match.
91
+		scheme = "https"
92
+		host = "www.google.com"
93
+		path = "/product/42"
94
+		query = "?foo=bar"
95
+		method = "GET"
96
+		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
97
+		resultVars = map[bool]map[string]string{
98
+			true:  map[string]string{"var1": "www", "var2": "product", "var3": "42"},
99
+			false: map[string]string{},
100
+		}
101
+	}
102
+
103
+	reset2 := func() {
104
+		// Everything match.
105
+		scheme = "http"
106
+		host = "www.google.com"
107
+		path = "/foo/product/42/path/that/is/ignored"
108
+		query = "?baz=ding"
109
+		method = "POST"
110
+		headers = map[string]string{"Content-Type": "application/json"}
111
+		resultVars = map[bool]map[string]string{
112
+			true:  map[string]string{"var4": "google", "var5": "product", "var6": "42"},
113
+			false: map[string]string{},
114
+		}
115
+	}
116
+
117
+	match := func(shouldMatch bool) {
118
+		url := scheme + "://" + host + path + query
119
+		request, _ := http.NewRequest(method, url, nil)
120
+		for key, value := range headers {
121
+			request.Header.Add(key, value)
122
+		}
123
+
124
+		var routeMatch RouteMatch
125
+		matched := router.Match(request, &routeMatch)
126
+		if matched != shouldMatch {
127
+			// Need better messages. :)
128
+			if matched {
129
+				t.Errorf("Should match.")
130
+			} else {
131
+				t.Errorf("Should not match.")
132
+			}
133
+		}
134
+
135
+		if matched {
136
+			currentRoute := routeMatch.Route
137
+			if currentRoute == nil {
138
+				t.Errorf("Expected a current route.")
139
+			}
140
+			vars := routeMatch.Vars
141
+			expectedVars := resultVars[shouldMatch]
142
+			if len(vars) != len(expectedVars) {
143
+				t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
144
+			}
145
+			for name, value := range vars {
146
+				if expectedVars[name] != value {
147
+					t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
148
+				}
149
+			}
150
+		}
151
+	}
152
+
153
+	// 1st route --------------------------------------------------------------
154
+
155
+	// Everything match.
156
+	reset()
157
+	match(true)
158
+
159
+	// Scheme doesn't match.
160
+	reset()
161
+	scheme = "http"
162
+	match(false)
163
+
164
+	// Host doesn't match.
165
+	reset()
166
+	host = "www.mygoogle.com"
167
+	match(false)
168
+
169
+	// Path doesn't match.
170
+	reset()
171
+	path = "/product/notdigits"
172
+	match(false)
173
+
174
+	// Query doesn't match.
175
+	reset()
176
+	query = "?foo=baz"
177
+	match(false)
178
+
179
+	// Method doesn't match.
180
+	reset()
181
+	method = "POST"
182
+	match(false)
183
+
184
+	// Header doesn't match.
185
+	reset()
186
+	headers = map[string]string{}
187
+	match(false)
188
+
189
+	// Everything match, again.
190
+	reset()
191
+	match(true)
192
+
193
+	// 2nd route --------------------------------------------------------------
194
+
195
+	// Everything match.
196
+	reset2()
197
+	match(true)
198
+
199
+	// Scheme doesn't match.
200
+	reset2()
201
+	scheme = "https"
202
+	match(false)
203
+
204
+	// Host doesn't match.
205
+	reset2()
206
+	host = "sub.google.com"
207
+	match(false)
208
+
209
+	// Path doesn't match.
210
+	reset2()
211
+	path = "/bar/product/42"
212
+	match(false)
213
+
214
+	// Query doesn't match.
215
+	reset2()
216
+	query = "?foo=baz"
217
+	match(false)
218
+
219
+	// Method doesn't match.
220
+	reset2()
221
+	method = "GET"
222
+	match(false)
223
+
224
+	// Header doesn't match.
225
+	reset2()
226
+	headers = map[string]string{}
227
+	match(false)
228
+
229
+	// Everything match, again.
230
+	reset2()
231
+	match(true)
232
+}
233
+
234
+type headerMatcherTest struct {
235
+	matcher headerMatcher
236
+	headers map[string]string
237
+	result  bool
238
+}
239
+
240
+var headerMatcherTests = []headerMatcherTest{
241
+	{
242
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
243
+		headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
244
+		result:  true,
245
+	},
246
+	{
247
+		matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
248
+		headers: map[string]string{"X-Requested-With": "anything"},
249
+		result:  true,
250
+	},
251
+	{
252
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
253
+		headers: map[string]string{},
254
+		result:  false,
255
+	},
256
+}
257
+
258
+type hostMatcherTest struct {
259
+	matcher *Route
260
+	url     string
261
+	vars    map[string]string
262
+	result  bool
263
+}
264
+
265
+var hostMatcherTests = []hostMatcherTest{
266
+	{
267
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
268
+		url:     "http://abc.def.ghi/",
269
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
270
+		result:  true,
271
+	},
272
+	{
273
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
274
+		url:     "http://a.b.c/",
275
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
276
+		result:  false,
277
+	},
278
+}
279
+
280
+type methodMatcherTest struct {
281
+	matcher methodMatcher
282
+	method  string
283
+	result  bool
284
+}
285
+
286
+var methodMatcherTests = []methodMatcherTest{
287
+	{
288
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
289
+		method:  "GET",
290
+		result:  true,
291
+	},
292
+	{
293
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
294
+		method:  "POST",
295
+		result:  true,
296
+	},
297
+	{
298
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
299
+		method:  "PUT",
300
+		result:  true,
301
+	},
302
+	{
303
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
304
+		method:  "DELETE",
305
+		result:  false,
306
+	},
307
+}
308
+
309
+type pathMatcherTest struct {
310
+	matcher *Route
311
+	url     string
312
+	vars    map[string]string
313
+	result  bool
314
+}
315
+
316
+var pathMatcherTests = []pathMatcherTest{
317
+	{
318
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
319
+		url:     "http://localhost:8080/123/456/789",
320
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
321
+		result:  true,
322
+	},
323
+	{
324
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
325
+		url:     "http://localhost:8080/1/2/3",
326
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
327
+		result:  false,
328
+	},
329
+}
330
+
331
+type queryMatcherTest struct {
332
+	matcher queryMatcher
333
+	url     string
334
+	result  bool
335
+}
336
+
337
+var queryMatcherTests = []queryMatcherTest{
338
+	{
339
+		matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}),
340
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
341
+		result:  true,
342
+	},
343
+	{
344
+		matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}),
345
+		url:     "http://localhost:8080/?foo=anything&baz=anything",
346
+		result:  true,
347
+	},
348
+	{
349
+		matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}),
350
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
351
+		result:  false,
352
+	},
353
+	{
354
+		matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}),
355
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
356
+		result:  false,
357
+	},
358
+}
359
+
360
+type schemeMatcherTest struct {
361
+	matcher schemeMatcher
362
+	url     string
363
+	result  bool
364
+}
365
+
366
+var schemeMatcherTests = []schemeMatcherTest{
367
+	{
368
+		matcher: schemeMatcher([]string{"http", "https"}),
369
+		url:     "http://localhost:8080/",
370
+		result:  true,
371
+	},
372
+	{
373
+		matcher: schemeMatcher([]string{"http", "https"}),
374
+		url:     "https://localhost:8080/",
375
+		result:  true,
376
+	},
377
+	{
378
+		matcher: schemeMatcher([]string{"https"}),
379
+		url:     "http://localhost:8080/",
380
+		result:  false,
381
+	},
382
+	{
383
+		matcher: schemeMatcher([]string{"http"}),
384
+		url:     "https://localhost:8080/",
385
+		result:  false,
386
+	},
387
+}
388
+
389
+type urlBuildingTest struct {
390
+	route *Route
391
+	vars  []string
392
+	url   string
393
+}
394
+
395
+var urlBuildingTests = []urlBuildingTest{
396
+	{
397
+		route: new(Route).Host("foo.domain.com"),
398
+		vars:  []string{},
399
+		url:   "http://foo.domain.com",
400
+	},
401
+	{
402
+		route: new(Route).Host("{subdomain}.domain.com"),
403
+		vars:  []string{"subdomain", "bar"},
404
+		url:   "http://bar.domain.com",
405
+	},
406
+	{
407
+		route: new(Route).Host("foo.domain.com").Path("/articles"),
408
+		vars:  []string{},
409
+		url:   "http://foo.domain.com/articles",
410
+	},
411
+	{
412
+		route: new(Route).Path("/articles"),
413
+		vars:  []string{},
414
+		url:   "/articles",
415
+	},
416
+	{
417
+		route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
418
+		vars:  []string{"category", "technology", "id", "42"},
419
+		url:   "/articles/technology/42",
420
+	},
421
+	{
422
+		route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
423
+		vars:  []string{"subdomain", "foo", "category", "technology", "id", "42"},
424
+		url:   "http://foo.domain.com/articles/technology/42",
425
+	},
426
+}
427
+
428
+func TestHeaderMatcher(t *testing.T) {
429
+	for _, v := range headerMatcherTests {
430
+		request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
431
+		for key, value := range v.headers {
432
+			request.Header.Add(key, value)
433
+		}
434
+		var routeMatch RouteMatch
435
+		result := v.matcher.Match(request, &routeMatch)
436
+		if result != v.result {
437
+			if v.result {
438
+				t.Errorf("%#v: should match %v.", v.matcher, request.Header)
439
+			} else {
440
+				t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
441
+			}
442
+		}
443
+	}
444
+}
445
+
446
+func TestHostMatcher(t *testing.T) {
447
+	for _, v := range hostMatcherTests {
448
+		request, _ := http.NewRequest("GET", v.url, nil)
449
+		var routeMatch RouteMatch
450
+		result := v.matcher.Match(request, &routeMatch)
451
+		vars := routeMatch.Vars
452
+		if result != v.result {
453
+			if v.result {
454
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
455
+			} else {
456
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
457
+			}
458
+		}
459
+		if result {
460
+			if len(vars) != len(v.vars) {
461
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
462
+			}
463
+			for name, value := range vars {
464
+				if v.vars[name] != value {
465
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
466
+				}
467
+			}
468
+		} else {
469
+			if len(vars) != 0 {
470
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
471
+			}
472
+		}
473
+	}
474
+}
475
+
476
+func TestMethodMatcher(t *testing.T) {
477
+	for _, v := range methodMatcherTests {
478
+		request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
479
+		var routeMatch RouteMatch
480
+		result := v.matcher.Match(request, &routeMatch)
481
+		if result != v.result {
482
+			if v.result {
483
+				t.Errorf("%#v: should match %v.", v.matcher, v.method)
484
+			} else {
485
+				t.Errorf("%#v: should not match %v.", v.matcher, v.method)
486
+			}
487
+		}
488
+	}
489
+}
490
+
491
+func TestPathMatcher(t *testing.T) {
492
+	for _, v := range pathMatcherTests {
493
+		request, _ := http.NewRequest("GET", v.url, nil)
494
+		var routeMatch RouteMatch
495
+		result := v.matcher.Match(request, &routeMatch)
496
+		vars := routeMatch.Vars
497
+		if result != v.result {
498
+			if v.result {
499
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
500
+			} else {
501
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
502
+			}
503
+		}
504
+		if result {
505
+			if len(vars) != len(v.vars) {
506
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
507
+			}
508
+			for name, value := range vars {
509
+				if v.vars[name] != value {
510
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
511
+				}
512
+			}
513
+		} else {
514
+			if len(vars) != 0 {
515
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
516
+			}
517
+		}
518
+	}
519
+}
520
+
521
+func TestQueryMatcher(t *testing.T) {
522
+	for _, v := range queryMatcherTests {
523
+		request, _ := http.NewRequest("GET", v.url, nil)
524
+		var routeMatch RouteMatch
525
+		result := v.matcher.Match(request, &routeMatch)
526
+		if result != v.result {
527
+			if v.result {
528
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
529
+			} else {
530
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
531
+			}
532
+		}
533
+	}
534
+}
535
+
536
+func TestSchemeMatcher(t *testing.T) {
537
+	for _, v := range queryMatcherTests {
538
+		request, _ := http.NewRequest("GET", v.url, nil)
539
+		var routeMatch RouteMatch
540
+		result := v.matcher.Match(request, &routeMatch)
541
+		if result != v.result {
542
+			if v.result {
543
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
544
+			} else {
545
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
546
+			}
547
+		}
548
+	}
549
+}
550
+
551
+func TestUrlBuilding(t *testing.T) {
552
+
553
+	for _, v := range urlBuildingTests {
554
+		u, _ := v.route.URL(v.vars...)
555
+		url := u.String()
556
+		if url != v.url {
557
+			t.Errorf("expected %v, got %v", v.url, url)
558
+			/*
559
+				reversePath := ""
560
+				reverseHost := ""
561
+				if v.route.pathTemplate != nil {
562
+						reversePath = v.route.pathTemplate.Reverse
563
+				}
564
+				if v.route.hostTemplate != nil {
565
+						reverseHost = v.route.hostTemplate.Reverse
566
+				}
567
+
568
+				t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
569
+			*/
570
+		}
571
+	}
572
+
573
+	ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
574
+	}
575
+
576
+	router := NewRouter()
577
+	router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
578
+
579
+	url, _ := router.Get("article").URL("category", "technology", "id", "42")
580
+	expected := "/articles/technology/42"
581
+	if url.String() != expected {
582
+		t.Errorf("Expected %v, got %v", expected, url.String())
583
+	}
584
+}
585
+
586
+func TestMatchedRouteName(t *testing.T) {
587
+	routeName := "stock"
588
+	router := NewRouter()
589
+	route := router.NewRoute().Path("/products/").Name(routeName)
590
+
591
+	url := "http://www.domain.com/products/"
592
+	request, _ := http.NewRequest("GET", url, nil)
593
+	var rv RouteMatch
594
+	ok := router.Match(request, &rv)
595
+
596
+	if !ok || rv.Route != route {
597
+		t.Errorf("Expected same route, got %+v.", rv.Route)
598
+	}
599
+
600
+	retName := rv.Route.GetName()
601
+	if retName != routeName {
602
+		t.Errorf("Expected %q, got %q.", routeName, retName)
603
+	}
604
+}
605
+
606
+func TestSubRouting(t *testing.T) {
607
+	// Example from docs.
608
+	router := NewRouter()
609
+	subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
610
+	route := subrouter.NewRoute().Path("/products/").Name("products")
611
+
612
+	url := "http://www.domain.com/products/"
613
+	request, _ := http.NewRequest("GET", url, nil)
614
+	var rv RouteMatch
615
+	ok := router.Match(request, &rv)
616
+
617
+	if !ok || rv.Route != route {
618
+		t.Errorf("Expected same route, got %+v.", rv.Route)
619
+	}
620
+
621
+	u, _ := router.Get("products").URL()
622
+	builtUrl := u.String()
623
+	// Yay, subroute aware of the domain when building!
624
+	if builtUrl != url {
625
+		t.Errorf("Expected %q, got %q.", url, builtUrl)
626
+	}
627
+}
628
+
629
+func TestVariableNames(t *testing.T) {
630
+	route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
631
+	if route.err == nil {
632
+		t.Errorf("Expected error for duplicated variable names")
633
+	}
634
+}
635
+
636
+func TestRedirectSlash(t *testing.T) {
637
+	var route *Route
638
+	var routeMatch RouteMatch
639
+	r := NewRouter()
640
+
641
+	r.StrictSlash(false)
642
+	route = r.NewRoute()
643
+	if route.strictSlash != false {
644
+		t.Errorf("Expected false redirectSlash.")
645
+	}
646
+
647
+	r.StrictSlash(true)
648
+	route = r.NewRoute()
649
+	if route.strictSlash != true {
650
+		t.Errorf("Expected true redirectSlash.")
651
+	}
652
+
653
+	route = new(Route)
654
+	route.strictSlash = true
655
+	route.Path("/{arg1}/{arg2:[0-9]+}/")
656
+	request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
657
+	routeMatch = RouteMatch{}
658
+	_ = route.Match(request, &routeMatch)
659
+	vars := routeMatch.Vars
660
+	if vars["arg1"] != "foo" {
661
+		t.Errorf("Expected foo.")
662
+	}
663
+	if vars["arg2"] != "123" {
664
+		t.Errorf("Expected 123.")
665
+	}
666
+	rsp := NewRecorder()
667
+	routeMatch.Handler.ServeHTTP(rsp, request)
668
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
669
+		t.Errorf("Expected redirect header.")
670
+	}
671
+
672
+	route = new(Route)
673
+	route.strictSlash = true
674
+	route.Path("/{arg1}/{arg2:[0-9]+}")
675
+	request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
676
+	routeMatch = RouteMatch{}
677
+	_ = route.Match(request, &routeMatch)
678
+	vars = routeMatch.Vars
679
+	if vars["arg1"] != "foo" {
680
+		t.Errorf("Expected foo.")
681
+	}
682
+	if vars["arg2"] != "123" {
683
+		t.Errorf("Expected 123.")
684
+	}
685
+	rsp = NewRecorder()
686
+	routeMatch.Handler.ServeHTTP(rsp, request)
687
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
688
+		t.Errorf("Expected redirect header.")
689
+	}
690
+}
691
+
692
+// Test for the new regexp library, still not available in stable Go.
693
+func TestNewRegexp(t *testing.T) {
694
+	var p *routeRegexp
695
+	var matches []string
696
+
697
+	tests := map[string]map[string][]string{
698
+		"/{foo:a{2}}": {
699
+			"/a":    nil,
700
+			"/aa":   {"aa"},
701
+			"/aaa":  nil,
702
+			"/aaaa": nil,
703
+		},
704
+		"/{foo:a{2,}}": {
705
+			"/a":    nil,
706
+			"/aa":   {"aa"},
707
+			"/aaa":  {"aaa"},
708
+			"/aaaa": {"aaaa"},
709
+		},
710
+		"/{foo:a{2,3}}": {
711
+			"/a":    nil,
712
+			"/aa":   {"aa"},
713
+			"/aaa":  {"aaa"},
714
+			"/aaaa": nil,
715
+		},
716
+		"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
717
+			"/a":       nil,
718
+			"/ab":      nil,
719
+			"/abc":     nil,
720
+			"/abcd":    nil,
721
+			"/abc/ab":  {"abc", "ab"},
722
+			"/abc/abc": nil,
723
+			"/abcd/ab": nil,
724
+		},
725
+		`/{foo:\w{3,}}/{bar:\d{2,}}`: {
726
+			"/a":        nil,
727
+			"/ab":       nil,
728
+			"/abc":      nil,
729
+			"/abc/1":    nil,
730
+			"/abc/12":   {"abc", "12"},
731
+			"/abcd/12":  {"abcd", "12"},
732
+			"/abcd/123": {"abcd", "123"},
733
+		},
734
+	}
735
+
736
+	for pattern, paths := range tests {
737
+		p, _ = newRouteRegexp(pattern, false, false, false)
738
+		for path, result := range paths {
739
+			matches = p.regexp.FindStringSubmatch(path)
740
+			if result == nil {
741
+				if matches != nil {
742
+					t.Errorf("%v should not match %v.", pattern, path)
743
+				}
744
+			} else {
745
+				if len(matches) != len(result)+1 {
746
+					t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
747
+				} else {
748
+					for k, v := range result {
749
+						if matches[k+1] != v {
750
+							t.Errorf("Expected %v, got %v.", v, matches[k+1])
751
+						}
752
+					}
753
+				}
754
+			}
755
+		}
756
+	}
757
+}
0 758
new file mode 100644
... ...
@@ -0,0 +1,247 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package mux
5
+
6
+import (
7
+	"bytes"
8
+	"fmt"
9
+	"net/http"
10
+	"net/url"
11
+	"regexp"
12
+	"strings"
13
+)
14
+
15
+// newRouteRegexp parses a route template and returns a routeRegexp,
16
+// used to match a host or path.
17
+//
18
+// It will extract named variables, assemble a regexp to be matched, create
19
+// a "reverse" template to build URLs and compile regexps to validate variable
20
+// values used in URL building.
21
+//
22
+// Previously we accepted only Python-like identifiers for variable
23
+// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
24
+// name and pattern can't be empty, and names can't contain a colon.
25
+func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) {
26
+	// Check if it is well-formed.
27
+	idxs, errBraces := braceIndices(tpl)
28
+	if errBraces != nil {
29
+		return nil, errBraces
30
+	}
31
+	// Backup the original.
32
+	template := tpl
33
+	// Now let's parse it.
34
+	defaultPattern := "[^/]+"
35
+	if matchHost {
36
+		defaultPattern = "[^.]+"
37
+		matchPrefix, strictSlash = false, false
38
+	}
39
+	if matchPrefix {
40
+		strictSlash = false
41
+	}
42
+	// Set a flag for strictSlash.
43
+	endSlash := false
44
+	if strictSlash && strings.HasSuffix(tpl, "/") {
45
+		tpl = tpl[:len(tpl)-1]
46
+		endSlash = true
47
+	}
48
+	varsN := make([]string, len(idxs)/2)
49
+	varsR := make([]*regexp.Regexp, len(idxs)/2)
50
+	pattern := bytes.NewBufferString("^")
51
+	reverse := bytes.NewBufferString("")
52
+	var end int
53
+	var err error
54
+	for i := 0; i < len(idxs); i += 2 {
55
+		// Set all values we are interested in.
56
+		raw := tpl[end:idxs[i]]
57
+		end = idxs[i+1]
58
+		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
59
+		name := parts[0]
60
+		patt := defaultPattern
61
+		if len(parts) == 2 {
62
+			patt = parts[1]
63
+		}
64
+		// Name or pattern can't be empty.
65
+		if name == "" || patt == "" {
66
+			return nil, fmt.Errorf("mux: missing name or pattern in %q",
67
+				tpl[idxs[i]:end])
68
+		}
69
+		// Build the regexp pattern.
70
+		fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
71
+		// Build the reverse template.
72
+		fmt.Fprintf(reverse, "%s%%s", raw)
73
+		// Append variable name and compiled pattern.
74
+		varsN[i/2] = name
75
+		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
76
+		if err != nil {
77
+			return nil, err
78
+		}
79
+	}
80
+	// Add the remaining.
81
+	raw := tpl[end:]
82
+	pattern.WriteString(regexp.QuoteMeta(raw))
83
+	if strictSlash {
84
+		pattern.WriteString("[/]?")
85
+	}
86
+	if !matchPrefix {
87
+		pattern.WriteByte('$')
88
+	}
89
+	reverse.WriteString(raw)
90
+	if endSlash {
91
+		reverse.WriteByte('/')
92
+	}
93
+	// Compile full regexp.
94
+	reg, errCompile := regexp.Compile(pattern.String())
95
+	if errCompile != nil {
96
+		return nil, errCompile
97
+	}
98
+	// Done!
99
+	return &routeRegexp{
100
+		template:  template,
101
+		matchHost: matchHost,
102
+		regexp:    reg,
103
+		reverse:   reverse.String(),
104
+		varsN:     varsN,
105
+		varsR:     varsR,
106
+	}, nil
107
+}
108
+
109
+// routeRegexp stores a regexp to match a host or path and information to
110
+// collect and validate route variables.
111
+type routeRegexp struct {
112
+	// The unmodified template.
113
+	template string
114
+	// True for host match, false for path match.
115
+	matchHost bool
116
+	// Expanded regexp.
117
+	regexp *regexp.Regexp
118
+	// Reverse template.
119
+	reverse string
120
+	// Variable names.
121
+	varsN []string
122
+	// Variable regexps (validators).
123
+	varsR []*regexp.Regexp
124
+}
125
+
126
+// Match matches the regexp against the URL host or path.
127
+func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
128
+	if !r.matchHost {
129
+		return r.regexp.MatchString(req.URL.Path)
130
+	}
131
+	return r.regexp.MatchString(getHost(req))
132
+}
133
+
134
+// url builds a URL part using the given values.
135
+func (r *routeRegexp) url(pairs ...string) (string, error) {
136
+	values, err := mapFromPairs(pairs...)
137
+	if err != nil {
138
+		return "", err
139
+	}
140
+	urlValues := make([]interface{}, len(r.varsN))
141
+	for k, v := range r.varsN {
142
+		value, ok := values[v]
143
+		if !ok {
144
+			return "", fmt.Errorf("mux: missing route variable %q", v)
145
+		}
146
+		urlValues[k] = value
147
+	}
148
+	rv := fmt.Sprintf(r.reverse, urlValues...)
149
+	if !r.regexp.MatchString(rv) {
150
+		// The URL is checked against the full regexp, instead of checking
151
+		// individual variables. This is faster but to provide a good error
152
+		// message, we check individual regexps if the URL doesn't match.
153
+		for k, v := range r.varsN {
154
+			if !r.varsR[k].MatchString(values[v]) {
155
+				return "", fmt.Errorf(
156
+					"mux: variable %q doesn't match, expected %q", values[v],
157
+					r.varsR[k].String())
158
+			}
159
+		}
160
+	}
161
+	return rv, nil
162
+}
163
+
164
+// braceIndices returns the first level curly brace indices from a string.
165
+// It returns an error in case of unbalanced braces.
166
+func braceIndices(s string) ([]int, error) {
167
+	var level, idx int
168
+	idxs := make([]int, 0)
169
+	for i := 0; i < len(s); i++ {
170
+		switch s[i] {
171
+		case '{':
172
+			if level++; level == 1 {
173
+				idx = i
174
+			}
175
+		case '}':
176
+			if level--; level == 0 {
177
+				idxs = append(idxs, idx, i+1)
178
+			} else if level < 0 {
179
+				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
180
+			}
181
+		}
182
+	}
183
+	if level != 0 {
184
+		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
185
+	}
186
+	return idxs, nil
187
+}
188
+
189
+// ----------------------------------------------------------------------------
190
+// routeRegexpGroup
191
+// ----------------------------------------------------------------------------
192
+
193
+// routeRegexpGroup groups the route matchers that carry variables.
194
+type routeRegexpGroup struct {
195
+	host *routeRegexp
196
+	path *routeRegexp
197
+}
198
+
199
+// setMatch extracts the variables from the URL once a route matches.
200
+func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
201
+	// Store host variables.
202
+	if v.host != nil {
203
+		hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
204
+		if hostVars != nil {
205
+			for k, v := range v.host.varsN {
206
+				m.Vars[v] = hostVars[k+1]
207
+			}
208
+		}
209
+	}
210
+	// Store path variables.
211
+	if v.path != nil {
212
+		pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
213
+		if pathVars != nil {
214
+			for k, v := range v.path.varsN {
215
+				m.Vars[v] = pathVars[k+1]
216
+			}
217
+			// Check if we should redirect.
218
+			if r.strictSlash {
219
+				p1 := strings.HasSuffix(req.URL.Path, "/")
220
+				p2 := strings.HasSuffix(v.path.template, "/")
221
+				if p1 != p2 {
222
+					u, _ := url.Parse(req.URL.String())
223
+					if p1 {
224
+						u.Path = u.Path[:len(u.Path)-1]
225
+					} else {
226
+						u.Path += "/"
227
+					}
228
+					m.Handler = http.RedirectHandler(u.String(), 301)
229
+				}
230
+			}
231
+		}
232
+	}
233
+}
234
+
235
+// getHost tries its best to return the request host.
236
+func getHost(r *http.Request) string {
237
+	if !r.URL.IsAbs() {
238
+		host := r.Host
239
+		// Slice off any port information.
240
+		if i := strings.Index(host, ":"); i != -1 {
241
+			host = host[:i]
242
+		}
243
+		return host
244
+	}
245
+	return r.URL.Host
246
+}
0 247
new file mode 100644
... ...
@@ -0,0 +1,499 @@
0
+// Copyright 2012 The Gorilla Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+package mux
5
+
6
+import (
7
+	"errors"
8
+	"fmt"
9
+	"net/http"
10
+	"net/url"
11
+	"strings"
12
+)
13
+
14
+// Route stores information to match a request and build URLs.
15
+type Route struct {
16
+	// Parent where the route was registered (a Router).
17
+	parent parentRoute
18
+	// Request handler for the route.
19
+	handler http.Handler
20
+	// List of matchers.
21
+	matchers []matcher
22
+	// Manager for the variables from host and path.
23
+	regexp *routeRegexpGroup
24
+	// If true, when the path pattern is "/path/", accessing "/path" will
25
+	// redirect to the former and vice versa.
26
+	strictSlash bool
27
+	// If true, this route never matches: it is only used to build URLs.
28
+	buildOnly bool
29
+	// The name used to build URLs.
30
+	name string
31
+	// Error resulted from building a route.
32
+	err error
33
+}
34
+
35
+// Match matches the route against the request.
36
+func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
37
+	if r.buildOnly || r.err != nil {
38
+		return false
39
+	}
40
+	// Match everything.
41
+	for _, m := range r.matchers {
42
+		if matched := m.Match(req, match); !matched {
43
+			return false
44
+		}
45
+	}
46
+	// Yay, we have a match. Let's collect some info about it.
47
+	if match.Route == nil {
48
+		match.Route = r
49
+	}
50
+	if match.Handler == nil {
51
+		match.Handler = r.handler
52
+	}
53
+	if match.Vars == nil {
54
+		match.Vars = make(map[string]string)
55
+	}
56
+	// Set variables.
57
+	if r.regexp != nil {
58
+		r.regexp.setMatch(req, match, r)
59
+	}
60
+	return true
61
+}
62
+
63
+// ----------------------------------------------------------------------------
64
+// Route attributes
65
+// ----------------------------------------------------------------------------
66
+
67
+// GetError returns an error resulted from building the route, if any.
68
+func (r *Route) GetError() error {
69
+	return r.err
70
+}
71
+
72
+// BuildOnly sets the route to never match: it is only used to build URLs.
73
+func (r *Route) BuildOnly() *Route {
74
+	r.buildOnly = true
75
+	return r
76
+}
77
+
78
+// Handler --------------------------------------------------------------------
79
+
80
+// Handler sets a handler for the route.
81
+func (r *Route) Handler(handler http.Handler) *Route {
82
+	if r.err == nil {
83
+		r.handler = handler
84
+	}
85
+	return r
86
+}
87
+
88
+// HandlerFunc sets a handler function for the route.
89
+func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
90
+	return r.Handler(http.HandlerFunc(f))
91
+}
92
+
93
+// GetHandler returns the handler for the route, if any.
94
+func (r *Route) GetHandler() http.Handler {
95
+	return r.handler
96
+}
97
+
98
+// Name -----------------------------------------------------------------------
99
+
100
+// Name sets the name for the route, used to build URLs.
101
+// If the name was registered already it will be overwritten.
102
+func (r *Route) Name(name string) *Route {
103
+	if r.name != "" {
104
+		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
105
+			r.name, name)
106
+	}
107
+	if r.err == nil {
108
+		r.name = name
109
+		r.getNamedRoutes()[name] = r
110
+	}
111
+	return r
112
+}
113
+
114
+// GetName returns the name for the route, if any.
115
+func (r *Route) GetName() string {
116
+	return r.name
117
+}
118
+
119
+// ----------------------------------------------------------------------------
120
+// Matchers
121
+// ----------------------------------------------------------------------------
122
+
123
+// matcher types try to match a request.
124
+type matcher interface {
125
+	Match(*http.Request, *RouteMatch) bool
126
+}
127
+
128
+// addMatcher adds a matcher to the route.
129
+func (r *Route) addMatcher(m matcher) *Route {
130
+	if r.err == nil {
131
+		r.matchers = append(r.matchers, m)
132
+	}
133
+	return r
134
+}
135
+
136
+// addRegexpMatcher adds a host or path matcher and builder to a route.
137
+func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error {
138
+	if r.err != nil {
139
+		return r.err
140
+	}
141
+	r.regexp = r.getRegexpGroup()
142
+	if !matchHost {
143
+		if len(tpl) == 0 || tpl[0] != '/' {
144
+			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
145
+		}
146
+		if r.regexp.path != nil {
147
+			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
148
+		}
149
+	}
150
+	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash)
151
+	if err != nil {
152
+		return err
153
+	}
154
+	if matchHost {
155
+		if r.regexp.path != nil {
156
+			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
157
+				return err
158
+			}
159
+		}
160
+		r.regexp.host = rr
161
+	} else {
162
+		if r.regexp.host != nil {
163
+			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
164
+				return err
165
+			}
166
+		}
167
+		r.regexp.path = rr
168
+	}
169
+	r.addMatcher(rr)
170
+	return nil
171
+}
172
+
173
+// Headers --------------------------------------------------------------------
174
+
175
+// headerMatcher matches the request against header values.
176
+type headerMatcher map[string]string
177
+
178
+func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
179
+	return matchMap(m, r.Header, true)
180
+}
181
+
182
+// Headers adds a matcher for request header values.
183
+// It accepts a sequence of key/value pairs to be matched. For example:
184
+//
185
+//     r := mux.NewRouter()
186
+//     r.Headers("Content-Type", "application/json",
187
+//               "X-Requested-With", "XMLHttpRequest")
188
+//
189
+// The above route will only match if both request header values match.
190
+//
191
+// It the value is an empty string, it will match any value if the key is set.
192
+func (r *Route) Headers(pairs ...string) *Route {
193
+	if r.err == nil {
194
+		var headers map[string]string
195
+		headers, r.err = mapFromPairs(pairs...)
196
+		return r.addMatcher(headerMatcher(headers))
197
+	}
198
+	return r
199
+}
200
+
201
+// Host -----------------------------------------------------------------------
202
+
203
+// Host adds a matcher for the URL host.
204
+// It accepts a template with zero or more URL variables enclosed by {}.
205
+// Variables can define an optional regexp pattern to me matched:
206
+//
207
+// - {name} matches anything until the next dot.
208
+//
209
+// - {name:pattern} matches the given regexp pattern.
210
+//
211
+// For example:
212
+//
213
+//     r := mux.NewRouter()
214
+//     r.Host("www.domain.com")
215
+//     r.Host("{subdomain}.domain.com")
216
+//     r.Host("{subdomain:[a-z]+}.domain.com")
217
+//
218
+// Variable names must be unique in a given route. They can be retrieved
219
+// calling mux.Vars(request).
220
+func (r *Route) Host(tpl string) *Route {
221
+	r.err = r.addRegexpMatcher(tpl, true, false)
222
+	return r
223
+}
224
+
225
+// MatcherFunc ----------------------------------------------------------------
226
+
227
+// MatcherFunc is the function signature used by custom matchers.
228
+type MatcherFunc func(*http.Request, *RouteMatch) bool
229
+
230
+func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
231
+	return m(r, match)
232
+}
233
+
234
+// MatcherFunc adds a custom function to be used as request matcher.
235
+func (r *Route) MatcherFunc(f MatcherFunc) *Route {
236
+	return r.addMatcher(f)
237
+}
238
+
239
+// Methods --------------------------------------------------------------------
240
+
241
+// methodMatcher matches the request against HTTP methods.
242
+type methodMatcher []string
243
+
244
+func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
245
+	return matchInArray(m, r.Method)
246
+}
247
+
248
+// Methods adds a matcher for HTTP methods.
249
+// It accepts a sequence of one or more methods to be matched, e.g.:
250
+// "GET", "POST", "PUT".
251
+func (r *Route) Methods(methods ...string) *Route {
252
+	for k, v := range methods {
253
+		methods[k] = strings.ToUpper(v)
254
+	}
255
+	return r.addMatcher(methodMatcher(methods))
256
+}
257
+
258
+// Path -----------------------------------------------------------------------
259
+
260
+// Path adds a matcher for the URL path.
261
+// It accepts a template with zero or more URL variables enclosed by {}.
262
+// Variables can define an optional regexp pattern to me matched:
263
+//
264
+// - {name} matches anything until the next slash.
265
+//
266
+// - {name:pattern} matches the given regexp pattern.
267
+//
268
+// For example:
269
+//
270
+//     r := mux.NewRouter()
271
+//     r.Path("/products/").Handler(ProductsHandler)
272
+//     r.Path("/products/{key}").Handler(ProductsHandler)
273
+//     r.Path("/articles/{category}/{id:[0-9]+}").
274
+//       Handler(ArticleHandler)
275
+//
276
+// Variable names must be unique in a given route. They can be retrieved
277
+// calling mux.Vars(request).
278
+func (r *Route) Path(tpl string) *Route {
279
+	r.err = r.addRegexpMatcher(tpl, false, false)
280
+	return r
281
+}
282
+
283
+// PathPrefix -----------------------------------------------------------------
284
+
285
+// PathPrefix adds a matcher for the URL path prefix.
286
+func (r *Route) PathPrefix(tpl string) *Route {
287
+	r.strictSlash = false
288
+	r.err = r.addRegexpMatcher(tpl, false, true)
289
+	return r
290
+}
291
+
292
+// Query ----------------------------------------------------------------------
293
+
294
+// queryMatcher matches the request against URL queries.
295
+type queryMatcher map[string]string
296
+
297
+func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool {
298
+	return matchMap(m, r.URL.Query(), false)
299
+}
300
+
301
+// Queries adds a matcher for URL query values.
302
+// It accepts a sequence of key/value pairs. For example:
303
+//
304
+//     r := mux.NewRouter()
305
+//     r.Queries("foo", "bar", "baz", "ding")
306
+//
307
+// The above route will only match if the URL contains the defined queries
308
+// values, e.g.: ?foo=bar&baz=ding.
309
+//
310
+// It the value is an empty string, it will match any value if the key is set.
311
+func (r *Route) Queries(pairs ...string) *Route {
312
+	if r.err == nil {
313
+		var queries map[string]string
314
+		queries, r.err = mapFromPairs(pairs...)
315
+		return r.addMatcher(queryMatcher(queries))
316
+	}
317
+	return r
318
+}
319
+
320
+// Schemes --------------------------------------------------------------------
321
+
322
+// schemeMatcher matches the request against URL schemes.
323
+type schemeMatcher []string
324
+
325
+func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
326
+	return matchInArray(m, r.URL.Scheme)
327
+}
328
+
329
+// Schemes adds a matcher for URL schemes.
330
+// It accepts a sequence schemes to be matched, e.g.: "http", "https".
331
+func (r *Route) Schemes(schemes ...string) *Route {
332
+	for k, v := range schemes {
333
+		schemes[k] = strings.ToLower(v)
334
+	}
335
+	return r.addMatcher(schemeMatcher(schemes))
336
+}
337
+
338
+// Subrouter ------------------------------------------------------------------
339
+
340
+// Subrouter creates a subrouter for the route.
341
+//
342
+// It will test the inner routes only if the parent route matched. For example:
343
+//
344
+//     r := mux.NewRouter()
345
+//     s := r.Host("www.domain.com").Subrouter()
346
+//     s.HandleFunc("/products/", ProductsHandler)
347
+//     s.HandleFunc("/products/{key}", ProductHandler)
348
+//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
349
+//
350
+// Here, the routes registered in the subrouter won't be tested if the host
351
+// doesn't match.
352
+func (r *Route) Subrouter() *Router {
353
+	router := &Router{parent: r, strictSlash: r.strictSlash}
354
+	r.addMatcher(router)
355
+	return router
356
+}
357
+
358
+// ----------------------------------------------------------------------------
359
+// URL building
360
+// ----------------------------------------------------------------------------
361
+
362
+// URL builds a URL for the route.
363
+//
364
+// It accepts a sequence of key/value pairs for the route variables. For
365
+// example, given this route:
366
+//
367
+//     r := mux.NewRouter()
368
+//     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
369
+//       Name("article")
370
+//
371
+// ...a URL for it can be built using:
372
+//
373
+//     url, err := r.Get("article").URL("category", "technology", "id", "42")
374
+//
375
+// ...which will return an url.URL with the following path:
376
+//
377
+//     "/articles/technology/42"
378
+//
379
+// This also works for host variables:
380
+//
381
+//     r := mux.NewRouter()
382
+//     r.Host("{subdomain}.domain.com").
383
+//       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
384
+//       Name("article")
385
+//
386
+//     // url.String() will be "http://news.domain.com/articles/technology/42"
387
+//     url, err := r.Get("article").URL("subdomain", "news",
388
+//                                      "category", "technology",
389
+//                                      "id", "42")
390
+//
391
+// All variables defined in the route are required, and their values must
392
+// conform to the corresponding patterns.
393
+func (r *Route) URL(pairs ...string) (*url.URL, error) {
394
+	if r.err != nil {
395
+		return nil, r.err
396
+	}
397
+	if r.regexp == nil {
398
+		return nil, errors.New("mux: route doesn't have a host or path")
399
+	}
400
+	var scheme, host, path string
401
+	var err error
402
+	if r.regexp.host != nil {
403
+		// Set a default scheme.
404
+		scheme = "http"
405
+		if host, err = r.regexp.host.url(pairs...); err != nil {
406
+			return nil, err
407
+		}
408
+	}
409
+	if r.regexp.path != nil {
410
+		if path, err = r.regexp.path.url(pairs...); err != nil {
411
+			return nil, err
412
+		}
413
+	}
414
+	return &url.URL{
415
+		Scheme: scheme,
416
+		Host:   host,
417
+		Path:   path,
418
+	}, nil
419
+}
420
+
421
+// URLHost builds the host part of the URL for a route. See Route.URL().
422
+//
423
+// The route must have a host defined.
424
+func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
425
+	if r.err != nil {
426
+		return nil, r.err
427
+	}
428
+	if r.regexp == nil || r.regexp.host == nil {
429
+		return nil, errors.New("mux: route doesn't have a host")
430
+	}
431
+	host, err := r.regexp.host.url(pairs...)
432
+	if err != nil {
433
+		return nil, err
434
+	}
435
+	return &url.URL{
436
+		Scheme: "http",
437
+		Host:   host,
438
+	}, nil
439
+}
440
+
441
+// URLPath builds the path part of the URL for a route. See Route.URL().
442
+//
443
+// The route must have a path defined.
444
+func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
445
+	if r.err != nil {
446
+		return nil, r.err
447
+	}
448
+	if r.regexp == nil || r.regexp.path == nil {
449
+		return nil, errors.New("mux: route doesn't have a path")
450
+	}
451
+	path, err := r.regexp.path.url(pairs...)
452
+	if err != nil {
453
+		return nil, err
454
+	}
455
+	return &url.URL{
456
+		Path: path,
457
+	}, nil
458
+}
459
+
460
+// ----------------------------------------------------------------------------
461
+// parentRoute
462
+// ----------------------------------------------------------------------------
463
+
464
+// parentRoute allows routes to know about parent host and path definitions.
465
+type parentRoute interface {
466
+	getNamedRoutes() map[string]*Route
467
+	getRegexpGroup() *routeRegexpGroup
468
+}
469
+
470
+// getNamedRoutes returns the map where named routes are registered.
471
+func (r *Route) getNamedRoutes() map[string]*Route {
472
+	if r.parent == nil {
473
+		// During tests router is not always set.
474
+		r.parent = NewRouter()
475
+	}
476
+	return r.parent.getNamedRoutes()
477
+}
478
+
479
+// getRegexpGroup returns regexp definitions from this route.
480
+func (r *Route) getRegexpGroup() *routeRegexpGroup {
481
+	if r.regexp == nil {
482
+		if r.parent == nil {
483
+			// During tests router is not always set.
484
+			r.parent = NewRouter()
485
+		}
486
+		regexp := r.parent.getRegexpGroup()
487
+		if regexp == nil {
488
+			r.regexp = new(routeRegexpGroup)
489
+		} else {
490
+			// Copy.
491
+			r.regexp = &routeRegexpGroup{
492
+				host: regexp.host,
493
+				path: regexp.path,
494
+			}
495
+		}
496
+	}
497
+	return r.regexp
498
+}
0 499
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+[568].out
1
+_go*
2
+_test*
3
+_obj
0 4
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+Copyright (c) 2011 Keith Rarick
1
+
2
+Permission is hereby granted, free of charge, to any person
3
+obtaining a copy of this software and associated
4
+documentation files (the "Software"), to deal in the
5
+Software without restriction, including without limitation
6
+the rights to use, copy, modify, merge, publish, distribute,
7
+sublicense, and/or sell copies of the Software, and to
8
+permit persons to whom the Software is furnished to do so,
9
+subject to the following conditions:
10
+
11
+The above copyright notice and this permission notice shall
12
+be included in all copies or substantial portions of the
13
+Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
18
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
19
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 23
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+# pty
1
+
2
+Pty is a Go package for using unix pseudo-terminals.
3
+
4
+## Install
5
+
6
+    go get github.com/kr/pty
7
+
8
+## Example
9
+
10
+```go
11
+package main
12
+
13
+import (
14
+	"github.com/kr/pty"
15
+	"io"
16
+	"os"
17
+	"os/exec"
18
+)
19
+
20
+func main() {
21
+	c := exec.Command("grep", "--color=auto", "bar")
22
+	f, err := pty.Start(c)
23
+	if err != nil {
24
+		panic(err)
25
+	}
26
+
27
+	go func() {
28
+		f.Write([]byte("foo\n"))
29
+		f.Write([]byte("bar\n"))
30
+		f.Write([]byte("baz\n"))
31
+		f.Write([]byte{4}) // EOT
32
+	}()
33
+	io.Copy(os.Stdout, f)
34
+}
35
+```
0 36
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+// Package pty provides functions for working with Unix terminals.
1
+package pty
2
+
3
+import (
4
+	"os"
5
+)
6
+
7
+// Opens a pty and its corresponding tty.
8
+func Open() (pty, tty *os.File, err error) {
9
+	return open()
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+package pty
1
+
2
+import (
3
+	"errors"
4
+	"os"
5
+	"syscall"
6
+	"unsafe"
7
+)
8
+
9
+// see ioccom.h
10
+const sys_IOCPARM_MASK = 0x1fff
11
+
12
+func open() (pty, tty *os.File, err error) {
13
+	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
14
+	if err != nil {
15
+		return nil, nil, err
16
+	}
17
+
18
+	sname, err := ptsname(p)
19
+	if err != nil {
20
+		return nil, nil, err
21
+	}
22
+
23
+	err = grantpt(p)
24
+	if err != nil {
25
+		return nil, nil, err
26
+	}
27
+
28
+	err = unlockpt(p)
29
+	if err != nil {
30
+		return nil, nil, err
31
+	}
32
+
33
+	t, err := os.OpenFile(sname, os.O_RDWR, 0)
34
+	if err != nil {
35
+		return nil, nil, err
36
+	}
37
+	return p, t, nil
38
+}
39
+
40
+func ptsname(f *os.File) (string, error) {
41
+	var n [(syscall.TIOCPTYGNAME >> 16) & sys_IOCPARM_MASK]byte
42
+
43
+	ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n)))
44
+	for i, c := range n {
45
+		if c == 0 {
46
+			return string(n[:i]), nil
47
+		}
48
+	}
49
+	return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
50
+}
51
+
52
+func grantpt(f *os.File) error {
53
+	var u int
54
+	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, uintptr(unsafe.Pointer(&u)))
55
+}
56
+
57
+func unlockpt(f *os.File) error {
58
+	var u int
59
+	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
60
+}
61
+
62
+func ioctl(fd, cmd, ptr uintptr) error {
63
+	_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
64
+	if e != 0 {
65
+		return syscall.ENOTTY
66
+	}
67
+	return nil
68
+}
0 69
new file mode 100644
... ...
@@ -0,0 +1,63 @@
0
+package pty
1
+
2
+import (
3
+	"os"
4
+	"strconv"
5
+	"syscall"
6
+	"unsafe"
7
+)
8
+
9
+const (
10
+	sys_TIOCGPTN   = 0x80045430
11
+	sys_TIOCSPTLCK = 0x40045431
12
+)
13
+
14
+func open() (pty, tty *os.File, err error) {
15
+	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
16
+	if err != nil {
17
+		return nil, nil, err
18
+	}
19
+
20
+	sname, err := ptsname(p)
21
+	if err != nil {
22
+		return nil, nil, err
23
+	}
24
+
25
+	err = unlockpt(p)
26
+	if err != nil {
27
+		return nil, nil, err
28
+	}
29
+
30
+	t, err := os.OpenFile(sname, os.O_RDWR, 0)
31
+	if err != nil {
32
+		return nil, nil, err
33
+	}
34
+	return p, t, nil
35
+}
36
+
37
+func ptsname(f *os.File) (string, error) {
38
+	var n int
39
+	err := ioctl(f.Fd(), sys_TIOCGPTN, &n)
40
+	if err != nil {
41
+		return "", err
42
+	}
43
+	return "/dev/pts/" + strconv.Itoa(n), nil
44
+}
45
+
46
+func unlockpt(f *os.File) error {
47
+	var u int
48
+	return ioctl(f.Fd(), sys_TIOCSPTLCK, &u)
49
+}
50
+
51
+func ioctl(fd uintptr, cmd uintptr, data *int) error {
52
+	_, _, e := syscall.Syscall(
53
+		syscall.SYS_IOCTL,
54
+		fd,
55
+		cmd,
56
+		uintptr(unsafe.Pointer(data)),
57
+	)
58
+	if e != 0 {
59
+		return syscall.ENOTTY
60
+	}
61
+	return nil
62
+}
0 63
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package pty
1
+
2
+import (
3
+	"os"
4
+	"os/exec"
5
+	"syscall"
6
+)
7
+
8
+// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
9
+// and c.Stderr, calls c.Start, and returns the File of the tty's
10
+// corresponding pty.
11
+func Start(c *exec.Cmd) (pty *os.File, err error) {
12
+	pty, tty, err := Open()
13
+	if err != nil {
14
+		return nil, err
15
+	}
16
+	defer tty.Close()
17
+	c.Stdout = tty
18
+	c.Stdin = tty
19
+	c.Stderr = tty
20
+	c.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
21
+	err = c.Start()
22
+	if err != nil {
23
+		pty.Close()
24
+		return nil, err
25
+	}
26
+	return pty, err
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package pty
1
+
2
+import (
3
+	"os"
4
+	"syscall"
5
+	"unsafe"
6
+)
7
+
8
+// Getsize returns the number of rows (lines) and cols (positions
9
+// in each line) in terminal t.
10
+func Getsize(t *os.File) (rows, cols int, err error) {
11
+	var ws winsize
12
+	err = windowrect(&ws, t.Fd())
13
+	return int(ws.ws_row), int(ws.ws_col), err
14
+}
15
+
16
+type winsize struct {
17
+	ws_row    uint16
18
+	ws_col    uint16
19
+	ws_xpixel uint16
20
+	ws_ypixel uint16
21
+}
22
+
23
+func windowrect(ws *winsize, fd uintptr) error {
24
+	_, _, errno := syscall.Syscall(
25
+		syscall.SYS_IOCTL,
26
+		fd,
27
+		syscall.TIOCGWINSZ,
28
+		uintptr(unsafe.Pointer(ws)),
29
+	)
30
+	if errno != 0 {
31
+		return syscall.Errno(errno)
32
+	}
33
+	return nil
34
+}