Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions reparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,29 @@ func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
}

func decodeWindowsReparsePointData(b []byte, isMountPoint bool) (*ReparsePoint, error) {
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
nameOffset := 8 + binary.LittleEndian.Uint16(b[0:2])
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8])
nameLength := binary.LittleEndian.Uint16(b[2:4])
name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil {
return nil, err
}
return &ReparsePoint{Target: string(utf16.Decode(name)), IsMountPoint: isMountPoint, IsLxSymlink: false}, nil
target := string(utf16.Decode(name))
// SubstituteName uses NT path prefixes; convert back to Win32 form.
if strings.HasPrefix(target, `\??\UNC\`) {
target = `\\` + target[8:]
} else if strings.HasPrefix(target, `\??\`) {
rest := target[4:]
if len(rest) >= 2 && isDriveLetter(rest[0]) && rest[1] == ':' {
target = rest
} else {
target = `\\?\` + rest
}
}
return &ReparsePoint{Target: target, IsMountPoint: isMountPoint, IsLxSymlink: false}, nil
}

func decodeLxReparsePointData(b []byte) (*ReparsePoint, error) {
Expand Down
177 changes: 177 additions & 0 deletions reparse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//go:build windows

package winio

import (
"bytes"
"encoding/binary"
"testing"
"unicode/utf16"
)

// buildMountPointBuffer builds a raw REPARSE_DATA_BUFFER for a mount point.
func buildMountPointBuffer(substituteName, printName string) []byte {
subUTF16 := utf16.Encode([]rune(substituteName + "\x00"))
printUTF16 := utf16.Encode([]rune(printName + "\x00"))

subBytes := len(subUTF16) * 2
printBytes := len(printUTF16) * 2

// Length fields exclude the trailing NUL.
subNameLen := uint16((len(subUTF16) - 1) * 2)
printNameLen := uint16((len(printUTF16) - 1) * 2)

if printName == "" {
printUTF16 = nil
printBytes = 0
printNameLen = 0
}

pathBufSize := subBytes + printBytes
dataLen := 8 + pathBufSize

var buf bytes.Buffer
_ = binary.Write(&buf, binary.LittleEndian, uint32(reparseTagMountPoint))
_ = binary.Write(&buf, binary.LittleEndian, uint16(dataLen))
_ = binary.Write(&buf, binary.LittleEndian, uint16(0))
_ = binary.Write(&buf, binary.LittleEndian, uint16(0))
_ = binary.Write(&buf, binary.LittleEndian, subNameLen)
_ = binary.Write(&buf, binary.LittleEndian, uint16(subBytes))
_ = binary.Write(&buf, binary.LittleEndian, printNameLen)
_ = binary.Write(&buf, binary.LittleEndian, subUTF16)
if printUTF16 != nil {
_ = binary.Write(&buf, binary.LittleEndian, printUTF16)
}

return buf.Bytes()
}

func TestDecodeJunctionEmptyPrintName(t *testing.T) {
raw := buildMountPointBuffer(`\??\C:\test\tgt`, "")

rp, err := DecodeReparsePoint(raw)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if rp.Target != `C:\test\tgt` {
t.Errorf("target = %q, want %q", rp.Target, `C:\test\tgt`)
}
if !rp.IsMountPoint {
t.Error("ismountpoint should be true")
}
}

func TestDecodeJunctionBothNames(t *testing.T) {
raw := buildMountPointBuffer(`\??\C:\test\tgt`, `C:\test\tgt`)

rp, err := DecodeReparsePoint(raw)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if rp.Target != `C:\test\tgt` {
t.Errorf("target = %q, want %q", rp.Target, `C:\test\tgt`)
}
if !rp.IsMountPoint {
t.Error("ismountpoint should be true")
}
}

func TestDecodeJunctionUNCPath(t *testing.T) {
raw := buildMountPointBuffer(`\??\UNC\server\share`, `\\server\share`)

rp, err := DecodeReparsePoint(raw)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if rp.Target != `\\server\share` {
t.Errorf("target = %q, want %q", rp.Target, `\\server\share`)
}
}

func TestDecodeJunctionVolumeGUIDPath(t *testing.T) {
raw := buildMountPointBuffer(`\??\Volume{00000000-0000-0000-0000-000000000000}\test`, `\\?\Volume{00000000-0000-0000-0000-000000000000}\test`)

rp, err := DecodeReparsePoint(raw)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if rp.Target != `\\?\Volume{00000000-0000-0000-0000-000000000000}\test` {
t.Errorf("target = %q, want %q", rp.Target, `\\?\Volume{00000000-0000-0000-0000-000000000000}\test`)
}
}

func TestRoundTripMountPointUNC(t *testing.T) {
original := &ReparsePoint{
Target: `\\server\share`,
IsMountPoint: true,
}

encoded := EncodeReparsePoint(original)
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if decoded.Target != original.Target {
t.Errorf("target = %q, want %q", decoded.Target, original.Target)
}
if !decoded.IsMountPoint {
t.Error("ismountpoint should be true")
}
}

func TestRoundTripMountPoint(t *testing.T) {
original := &ReparsePoint{
Target: `C:\test\tgt`,
IsMountPoint: true,
}

encoded := EncodeReparsePoint(original)
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if decoded.Target != original.Target {
t.Errorf("target = %q, want %q", decoded.Target, original.Target)
}
if !decoded.IsMountPoint {
t.Error("ismountpoint should be true")
}
}

func TestRoundTripSymlink(t *testing.T) {
original := &ReparsePoint{
Target: `C:\test\tgt`,
IsMountPoint: false,
}

encoded := EncodeReparsePoint(original)
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if decoded.Target != original.Target {
t.Errorf("target = %q, want %q", decoded.Target, original.Target)
}
if decoded.IsMountPoint {
t.Error("ismountpoint should be false")
}
}

func TestRoundTripSymlinkVolumeGUIDPath(t *testing.T) {
original := &ReparsePoint{
Target: `\\?\Volume{00000000-0000-0000-0000-000000000000}\test`,
IsMountPoint: false,
}

encoded := EncodeReparsePoint(original)
decoded, err := DecodeReparsePoint(encoded)
if err != nil {
t.Fatalf("decode reparse point failed: %v", err)
}
if decoded.Target != original.Target {
t.Errorf("target = %q, want %q", decoded.Target, original.Target)
}
if decoded.IsMountPoint {
t.Error("ismountpoint should be false")
}
}