]>
Commit | Line | Data |
---|---|---|
1 | package hotline | |
2 | ||
3 | import ( | |
4 | "bufio" | |
5 | "bytes" | |
6 | "encoding/binary" | |
7 | "errors" | |
8 | "fmt" | |
9 | "io" | |
10 | "path/filepath" | |
11 | "strings" | |
12 | ) | |
13 | ||
14 | // FilePathItem represents the file or directory portion of a delimited file path (e.g. foo and bar in "/foo/bar") | |
15 | // Example bytes: | |
16 | // 00 00 | |
17 | // 09 | |
18 | // 73 75 62 66 6f 6c 64 65 72 "subfolder" | |
19 | type FilePathItem struct { | |
20 | Len byte | |
21 | Name []byte | |
22 | } | |
23 | ||
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) { | |
38 | if len(b) < 3 { | |
39 | return n, errors.New("buflen too small") | |
40 | } | |
41 | fpi.Len = b[2] | |
42 | fpi.Name = b[fileItemMinLen : fpi.Len+fileItemMinLen] | |
43 | ||
44 | return int(fpi.Len) + fileItemMinLen, nil | |
45 | } | |
46 | ||
47 | type FilePath struct { | |
48 | ItemCount [2]byte | |
49 | Items []FilePathItem | |
50 | } | |
51 | ||
52 | // Write implements io.Writer interface for FilePath | |
53 | func (fp *FilePath) Write(b []byte) (n int, err error) { | |
54 | reader := bytes.NewReader(b) | |
55 | err = binary.Read(reader, binary.BigEndian, &fp.ItemCount) | |
56 | if err != nil && !errors.Is(err, io.EOF) { | |
57 | return n, err | |
58 | } | |
59 | if errors.Is(err, io.EOF) { | |
60 | return n, nil | |
61 | } | |
62 | ||
63 | scanner := bufio.NewScanner(reader) | |
64 | scanner.Split(fileItemScanner) | |
65 | ||
66 | for i := 0; i < int(binary.BigEndian.Uint16(fp.ItemCount[:])); i++ { | |
67 | var fpi FilePathItem | |
68 | scanner.Scan() | |
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 { | |
76 | return n, err | |
77 | } | |
78 | fp.Items = append(fp.Items, fpi) | |
79 | } | |
80 | ||
81 | return n, nil | |
82 | } | |
83 | ||
84 | // IsDropbox checks if a FilePath matches the special drop box folder type | |
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 | ||
98 | return strings.Contains(strings.ToLower(string(fp.Items[fp.Len()-1].Name)), "upload") | |
99 | } | |
100 | ||
101 | func (fp *FilePath) Len() uint16 { | |
102 | return binary.BigEndian.Uint16(fp.ItemCount[:]) | |
103 | } | |
104 | ||
105 | func readPath(fileRoot string, filePath, fileName []byte) (fullPath string, err error) { | |
106 | var fp FilePath | |
107 | if filePath != nil { | |
108 | if _, err = fp.Write(filePath); err != nil { | |
109 | return "", err | |
110 | } | |
111 | } | |
112 | ||
113 | var subPath string | |
114 | for _, pathItem := range fp.Items { | |
115 | subPath = filepath.Join("/", subPath, string(pathItem.Name)) | |
116 | } | |
117 | ||
118 | fullPath = filepath.Join( | |
119 | fileRoot, | |
120 | subPath, | |
121 | filepath.Join("/", string(fileName)), | |
122 | ) | |
123 | fullPath, err = txtDecoder.String(fullPath) | |
124 | if err != nil { | |
125 | return "", fmt.Errorf("invalid filepath encoding: %w", err) | |
126 | } | |
127 | return fullPath, nil | |
128 | } |