...
1 package desktop
2
3 import (
4 "bufio"
5 "bytes"
6 "errors"
7 "fmt"
8 "io"
9 "strconv"
10 "strings"
11 )
12
13
14 type EntryType int
15
16
17 const (
18 Unknown EntryType = iota
19 Application
20 Link
21 Directory
22 )
23
24 const sectionHeaderNotFoundError = "section header not found"
25
26 func (t EntryType) String() string {
27 switch t {
28 case Unknown:
29 return "Unknown"
30 case Application:
31 return "Application"
32 case Link:
33 return "Link"
34 case Directory:
35 return "Directory"
36 }
37
38 return strconv.Itoa(int(t))
39 }
40
41 var (
42 entryHeader = []byte("[desktop entry]")
43 entryType = []byte("type=")
44 entryName = []byte("name=")
45 entryGenericName = []byte("genericname=")
46 entryComment = []byte("comment=")
47 entryIcon = []byte("icon=")
48 entryPath = []byte("path=")
49 entryExec = []byte("exec=")
50 entryURL = []byte("url=")
51 entryTerminal = []byte("terminal=true")
52 entryNoDisplay = []byte("nodisplay=true")
53 entryHidden = []byte("hidden=true")
54 )
55
56 var quotes = map[string]string{
57 `%%`: `%`,
58 `\\\\ `: `\\ `,
59 `\\\\` + "`": `\\` + "`",
60 `\\\\$`: `\\$`,
61 `\\\\(`: `\\(`,
62 `\\\\)`: `\\)`,
63 `\\\\\`: `\\\`,
64 `\\\\\\\\`: `\\\\`,
65 }
66
67
68 type Entry struct {
69
70 Type EntryType
71
72
73 Name string
74
75
76 GenericName string
77
78
79 Comment string
80
81
82 Icon string
83
84
85 Path string
86
87
88 Exec string
89
90
91 URL string
92
93
94 Terminal bool
95 }
96
97
98 func (e *Entry) ExpandExec(args string) string {
99 ex := e.Exec
100
101 ex = strings.ReplaceAll(ex, "%F", args)
102 ex = strings.ReplaceAll(ex, "%f", args)
103 ex = strings.ReplaceAll(ex, "%U", args)
104 ex = strings.ReplaceAll(ex, "%u", args)
105
106 return ex
107 }
108
109 func unquoteExec(ex string) string {
110 for qs, qr := range quotes {
111 ex = strings.ReplaceAll(ex, qs, qr)
112 }
113
114 return ex
115 }
116
117
118 func Parse(content io.Reader, buf []byte) (*Entry, error) {
119 var (
120 scanner = bufio.NewScanner(content)
121 scannedBytes []byte
122 scannedBytesLen int
123
124 entry Entry
125 foundHeader bool
126 )
127
128 scanner.Buffer(buf, len(buf))
129 for scanner.Scan() {
130 scannedBytes = bytes.TrimSpace(scanner.Bytes())
131 scannedBytesLen = len(scannedBytes)
132
133 if scannedBytesLen == 0 || scannedBytes[0] == byte('#') {
134 continue
135 } else if scannedBytes[0] == byte('[') {
136 if !foundHeader {
137 if scannedBytesLen < 15 || !bytes.EqualFold(scannedBytes[0:15], entryHeader) {
138 return nil, errors.New(sectionHeaderNotFoundError)
139 }
140
141 foundHeader = true
142 } else {
143 break
144 }
145 } else if scannedBytesLen >= 6 && bytes.EqualFold(scannedBytes[0:5], entryType) {
146 t := strings.ToLower(string(scannedBytes[5:]))
147 switch t {
148 case "application":
149 entry.Type = Application
150 case "link":
151 entry.Type = Link
152 case "directory":
153 entry.Type = Directory
154 }
155 } else if scannedBytesLen >= 6 && bytes.EqualFold(scannedBytes[0:5], entryName) {
156 entry.Name = string(scannedBytes[5:])
157 } else if scannedBytesLen >= 13 && bytes.EqualFold(scannedBytes[0:12], entryGenericName) {
158 entry.GenericName = string(scannedBytes[12:])
159 } else if scannedBytesLen >= 9 && bytes.EqualFold(scannedBytes[0:8], entryComment) {
160 entry.Comment = string(scannedBytes[8:])
161 } else if scannedBytesLen >= 6 && bytes.EqualFold(scannedBytes[0:5], entryIcon) {
162 entry.Icon = string(scannedBytes[5:])
163 } else if scannedBytesLen >= 6 && bytes.EqualFold(scannedBytes[0:5], entryPath) {
164 entry.Path = string(scannedBytes[5:])
165 } else if scannedBytesLen >= 6 && bytes.EqualFold(scannedBytes[0:5], entryExec) {
166 entry.Exec = unquoteExec(string(scannedBytes[5:]))
167 } else if scannedBytesLen >= 5 && bytes.EqualFold(scannedBytes[0:4], entryURL) {
168 entry.URL = string(scannedBytes[4:])
169 } else if scannedBytesLen == 13 && bytes.EqualFold(scannedBytes, entryTerminal) {
170 entry.Terminal = true
171 } else if (scannedBytesLen == 14 && bytes.EqualFold(scannedBytes, entryNoDisplay)) || (scannedBytesLen == 11 && bytes.EqualFold(scannedBytes, entryHidden)) {
172 return nil, nil
173 }
174 }
175
176 err := scanner.Err()
177 if err == nil && !foundHeader {
178 err = errors.New(sectionHeaderNotFoundError)
179 }
180 if err != nil {
181 return nil, fmt.Errorf("failed to parse desktop entry: %s", err)
182 }
183
184 return &entry, nil
185 }
186
View as plain text