-
Notifications
You must be signed in to change notification settings - Fork 5
/
desktop_parser.go
189 lines (162 loc) · 4.35 KB
/
desktop_parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// This file contains functions to extract process info like and name and icon
// from Linux desktop files.
// This is a porting of the Python OpenSnitch UI python desktop parser:
// https://github.com/evilsocket/opensnitch/blob/ec6ecea/ui/opensnitch/desktop_parser.py
package main
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
_ "github.com/lucor/fyne-opensnitch/statik"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/rakyll/statik/fs"
)
var desktopApps map[string]desktopApp
var defaultIcon []byte
type desktopApp struct {
Name string
Exec string
Icon string
}
// init loads all desktopApp info and makes them available via the desktopApps map
func init() {
statikFS, err := fs.New()
if err != nil {
log.Fatal("Could not init the icon filesystem %v", err)
}
f, err := statikFS.Open("/hicolor_apps_scalable_org.gnome.Terminal.png")
if err != nil {
log.Fatal("Could not open the default icon file from VFS: %v", err)
}
defer f.Close()
defaultIcon, err = ioutil.ReadAll(f)
if err != nil {
log.Fatal("Could not read the default icon content from VFS: %v", err)
}
// fixes contains some adjustement for the desktopApp mappings
fixes := map[string]string{
"/opt/google/chrome/google-chrome": "/opt/google/chrome/chrome",
"/usr/lib/firefox/firefox.sh": "/usr/lib/firefox/firefox",
"/usr/bin/pidgin": "/usr/bin/pidgin.orig",
}
desktopApps = make(map[string]desktopApp)
for _, dp := range desktopPaths() {
desktopApp, err := parseDesktopFile(dp)
if err != nil {
log.Warning("Could not fetch icon for %q. Reason: %s", dp, err)
continue
}
fix, ok := fixes[desktopApp.Exec]
if ok {
desktopApp.Exec = fix
}
desktopApps[desktopApp.Exec] = desktopApp
}
}
// desktopPaths returns the paths containing desktop files
func desktopPaths() []string {
var desktopPaths []string
val, ok := os.LookupEnv("XDG_DATA_DIRS")
if !ok {
val = "/usr/share"
}
for _, v := range strings.Split(val, ":") {
pattern := filepath.Join(v, "applications", "*.desktop")
founds, err := filepath.Glob(pattern)
if founds == nil || err != nil {
continue
}
desktopPaths = append(desktopPaths, founds...)
}
return desktopPaths
}
// parseDesktopFile returns a desktopApp from a .desktop file
func parseDesktopFile(desktopFile string) (desktopApp, error) {
var desktopEntrySection bool
var da desktopApp
b, err := ioutil.ReadFile(desktopFile)
if err != nil {
return da, err
}
scanner := bufio.NewScanner(bytes.NewReader(b))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "[") {
desktopEntrySection = false
if strings.Contains(line, "Desktop Entry") {
desktopEntrySection = true
}
continue
}
if desktopEntrySection == false {
continue
}
if strings.HasPrefix(line, "Name=") {
da.Name = strings.TrimPrefix(line, "Name=")
continue
}
if strings.HasPrefix(line, "Exec=") {
execFile := strings.TrimPrefix(line, "Exec=")
execFile = parseExec(execFile)
execFile, err := resolvePath(execFile)
if err != nil {
return da, err
}
da.Exec = execFile
}
if strings.HasPrefix(line, "Icon=") {
icon := strings.TrimPrefix(line, "Icon=")
if icon == "" {
continue
}
if filepath.IsAbs(icon) {
da.Icon = icon
continue
}
icon = filepath.Join("/usr/share/icons/hicolor/48x48/apps", icon) + ".png"
icon, err = resolvePath(icon)
if err != nil {
continue
}
da.Icon = icon
}
}
if err := scanner.Err(); err != nil {
return da, err
}
return da, nil
}
// parseExec parse the Exec entry into the desktop file to return the executable
func parseExec(execFile string) string {
// remove stuff like %U
reRemoveParams := regexp.MustCompile(`%[a-zA-Z]+`)
//remove 'env .... command'
reEnv := regexp.MustCompile(`^env\s+[^\s]+\s`)
e := reRemoveParams.ReplaceAllString(execFile, "")
e = reEnv.ReplaceAllString(e, "")
// remove quotes
strings.ReplaceAll(e, "'", "")
e = strings.TrimSpace(e)
parts := strings.Split(e, " ")
execFile, err := exec.LookPath(parts[0])
if err != nil {
return ""
}
return execFile
}
// resolvePath returns the absolute path resolving the symlink, if any
func resolvePath(file string) (string, error) {
file, err := filepath.EvalSymlinks(file)
if err != nil {
return file, err
}
if filepath.IsAbs(file) {
return file, nil
}
return filepath.Abs(file)
}