1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4package cache
5
6import (
7	"fmt"
8	"path/filepath"
9	"syscall"
10)
11
12func init() {
13	checkPathCase = windowsCheckPathCase
14}
15
16func windowsCheckPathCase(path string) error {
17	// Back in the day, Windows used to have short and long filenames, and
18	// it still supports those APIs. GetLongPathName gets the real case for a
19	// path, so we can use it here. Inspired by
20	// http://stackoverflow.com/q/2113822.
21
22	// Short paths can be longer than long paths, and unicode, so be generous.
23	buflen := 4 * len(path)
24	namep, err := syscall.UTF16PtrFromString(path)
25	if err != nil {
26		return err
27	}
28	short := make([]uint16, buflen)
29	n, err := syscall.GetShortPathName(namep, &short[0], uint32(len(short)*2)) // buflen is in bytes.
30	if err != nil {
31		return err
32	}
33	if int(n) > len(short)*2 {
34		return fmt.Errorf("short buffer too short: %v vs %v*2", n, len(short))
35	}
36	long := make([]uint16, buflen)
37	n, err = syscall.GetLongPathName(&short[0], &long[0], uint32(len(long)*2))
38	if err != nil {
39		return err
40	}
41	if int(n) > len(long)*2 {
42		return fmt.Errorf("long buffer too short: %v vs %v*2", n, len(long))
43	}
44	longstr := syscall.UTF16ToString(long)
45
46	isRoot := func(p string) bool {
47		return p[len(p)-1] == filepath.Separator
48	}
49	for got, want := path, longstr; !isRoot(got) && !isRoot(want); got, want = filepath.Dir(got), filepath.Dir(want) {
50		if g, w := filepath.Base(got), filepath.Base(want); g != w {
51			return fmt.Errorf("case mismatch in path %q: component %q should be %q", path, g, w)
52		}
53	}
54	return nil
55}
56