]>
Commit | Line | Data |
---|---|---|
6988a057 JH |
1 | package hotline |
2 | ||
3 | import ( | |
8fc43f8e | 4 | "bufio" |
00d1ef67 | 5 | "bytes" |
6988a057 | 6 | "encoding/binary" |
00d1ef67 | 7 | "errors" |
2e1aec0f | 8 | "fmt" |
050407a3 | 9 | "io" |
f22acf38 | 10 | "path/filepath" |
7e2e07da | 11 | "strings" |
6988a057 JH |
12 | ) |
13 | ||
6988a057 | 14 | // FilePathItem represents the file or directory portion of a delimited file path (e.g. foo and bar in "/foo/bar") |
8fc43f8e | 15 | // Example bytes: |
6988a057 JH |
16 | // 00 00 |
17 | // 09 | |
8fc43f8e | 18 | // 73 75 62 66 6f 6c 64 65 72 "subfolder" |
6988a057 JH |
19 | type FilePathItem struct { |
20 | Len byte | |
21 | Name []byte | |
22 | } | |
23 | ||
8fc43f8e JH |
24 | const fileItemMinLen = 3 |
25 | ||
26 | // fileItemScanner implements bufio.SplitFunc for parsing incoming byte slices into complete tokens | |
27 | func fileItemScanner(data []byte, _ bool) (advance int, token []byte, err error) { | |
28 | if len(data) < fileItemMinLen { | |
29 | return 0, nil, nil | |
30 | } | |
31 | ||
32 | advance = fileItemMinLen + int(data[2]) | |
33 | return advance, data[0:advance], nil | |
34 | } | |
35 | ||
36 | // Write implements the io.Writer interface for FilePathItem | |
37 | func (fpi *FilePathItem) Write(b []byte) (n int, err error) { | |
469a6387 JH |
38 | if len(b) < 3 { |
39 | return n, errors.New("buflen too small") | |
40 | } | |
8fc43f8e JH |
41 | fpi.Len = b[2] |
42 | fpi.Name = b[fileItemMinLen : fpi.Len+fileItemMinLen] | |
43 | ||
44 | return int(fpi.Len) + fileItemMinLen, nil | |
45 | } | |
46 | ||
6988a057 | 47 | type FilePath struct { |
00d1ef67 | 48 | ItemCount [2]byte |
72dd37f1 | 49 | Items []FilePathItem |
6988a057 JH |
50 | } |
51 | ||
469a6387 | 52 | // Write implements io.Writer interface for FilePath |
8fc43f8e | 53 | func (fp *FilePath) Write(b []byte) (n int, err error) { |
050407a3 | 54 | reader := bytes.NewReader(b) |
8fc43f8e | 55 | err = binary.Read(reader, binary.BigEndian, &fp.ItemCount) |
050407a3 | 56 | if err != nil && !errors.Is(err, io.EOF) { |
8fc43f8e | 57 | return n, err |
00d1ef67 | 58 | } |
050407a3 | 59 | if errors.Is(err, io.EOF) { |
8fc43f8e | 60 | return n, nil |
050407a3 | 61 | } |
6988a057 | 62 | |
8fc43f8e JH |
63 | scanner := bufio.NewScanner(reader) |
64 | scanner.Split(fileItemScanner) | |
050407a3 | 65 | |
8fc43f8e JH |
66 | for i := 0; i < int(binary.BigEndian.Uint16(fp.ItemCount[:])); i++ { |
67 | var fpi FilePathItem | |
68 | scanner.Scan() | |
469a6387 JH |
69 | |
70 | // Make a new []byte slice and copy the scanner bytes to it. This is critical to avoid a data race as the | |
71 | // scanner re-uses the buffer for subsequent scans. | |
72 | buf := make([]byte, len(scanner.Bytes())) | |
73 | copy(buf, scanner.Bytes()) | |
74 | ||
75 | if _, err := fpi.Write(buf); err != nil { | |
8fc43f8e | 76 | return n, err |
050407a3 | 77 | } |
8fc43f8e | 78 | fp.Items = append(fp.Items, fpi) |
6988a057 JH |
79 | } |
80 | ||
8fc43f8e | 81 | return n, nil |
72dd37f1 JH |
82 | } |
83 | ||
8fc43f8e | 84 | // IsDropbox checks if a FilePath matches the special drop box folder type |
7e2e07da JH |
85 | func (fp *FilePath) IsDropbox() bool { |
86 | if fp.Len() == 0 { | |
87 | return false | |
88 | } | |
89 | ||
90 | return strings.Contains(strings.ToLower(string(fp.Items[fp.Len()-1].Name)), "drop box") | |
91 | } | |
92 | ||
93 | func (fp *FilePath) IsUploadDir() bool { | |
94 | if fp.Len() == 0 { | |
95 | return false | |
96 | } | |
97 | ||
25f0d77d | 98 | return strings.Contains(strings.ToLower(string(fp.Items[fp.Len()-1].Name)), "upload") |
7e2e07da JH |
99 | } |
100 | ||
d34160c5 RBR |
101 | func (fp *FilePath) IsUserDir() bool { |
102 | if fp.Len() == 0 { | |
103 | return false | |
104 | } | |
105 | ||
106 | return strings.HasPrefix(string(fp.Items[0].Name), "~") | |
107 | } | |
108 | ||
109 | ||
72dd37f1 | 110 | func (fp *FilePath) Len() uint16 { |
00d1ef67 | 111 | return binary.BigEndian.Uint16(fp.ItemCount[:]) |
6988a057 JH |
112 | } |
113 | ||
fd740bc4 | 114 | func ReadPath(fileRoot string, filePath, fileName []byte) (fullPath string, err error) { |
92a7e455 JH |
115 | var fp FilePath |
116 | if filePath != nil { | |
8fc43f8e | 117 | if _, err = fp.Write(filePath); err != nil { |
92a7e455 JH |
118 | return "", err |
119 | } | |
120 | } | |
121 | ||
2e08be58 JH |
122 | var subPath string |
123 | for _, pathItem := range fp.Items { | |
124 | subPath = filepath.Join("/", subPath, string(pathItem.Name)) | |
125 | } | |
126 | ||
f22acf38 | 127 | fullPath = filepath.Join( |
92a7e455 | 128 | fileRoot, |
2e08be58 | 129 | subPath, |
f22acf38 | 130 | filepath.Join("/", string(fileName)), |
92a7e455 | 131 | ) |
2e1aec0f JH |
132 | fullPath, err = txtDecoder.String(fullPath) |
133 | if err != nil { | |
134 | return "", fmt.Errorf("invalid filepath encoding: %w", err) | |
135 | } | |
92a7e455 JH |
136 | return fullPath, nil |
137 | } |