summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeinrich Wendel <h_wendel@cojobo.net>2010-01-24 22:51:46 +0000
committerHeinrich Wendel <h_wendel@cojobo.net>2010-01-24 22:51:46 +0000
commit2d8c99e61006035b6ceba05fb5f778f11bc440c4 (patch)
tree2a6dbc53ee577f7ce6543b6324f33b1923e428c4
parentb32fbd8b3d0608701299ea1c95bd01e726a20090 (diff)
bug fixes, see changelog
-rw-r--r--ChangeLog7
-rwxr-xr-xtest/test-desktop.py4
-rw-r--r--xdg/DesktopEntry.py230
-rw-r--r--xdg/IniFile.py38
-rw-r--r--xdg/Mime.py2
5 files changed, 158 insertions, 123 deletions
diff --git a/ChangeLog b/ChangeLog
index cbbf143..65b6e8b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Version 0.19
+ * IniFile.py: add support for trusted desktop files (thanks to karl mikaelsson)
+ * DesktopEntry.py: Support spec version 1.0, Debian bug #563660
+ * MimeType.py: Fix parsing of in memory data, Debian bug #563718
+ * DesktopEntry.py: Fix constructor, Debian bug #551297, #562951, #562952
+
+
Version 0.18
* DesktopEntry.py: Add getMimeTypes() method, correctly returning strings
diff --git a/test/test-desktop.py b/test/test-desktop.py
index 831c368..28ff52b 100755
--- a/test/test-desktop.py
+++ b/test/test-desktop.py
@@ -9,14 +9,12 @@ def checkfiles(path):
for file in ls:
checkfiles(os.path.join(path, file))
else:
- entry = DesktopEntry()
try:
- entry.parse(path)
+ entry = DesktopEntry(path)
except ParsingError, e:
print e
return
- #entry.setLocale("C")
entry.getName()
try:
diff --git a/xdg/DesktopEntry.py b/xdg/DesktopEntry.py
index 5dd8e62..8626d7f 100644
--- a/xdg/DesktopEntry.py
+++ b/xdg/DesktopEntry.py
@@ -35,24 +35,27 @@ class DesktopEntry(IniFile):
# start standard keys
def getType(self):
return self.get('Type')
+ """ @deprecated, use getVersionString instead """
def getVersion(self):
return self.get('Version', type="numeric")
- def getEncoding(self):
- return self.get('Encoding')
+ def getVersionString(self):
+ return self.get('Version')
def getName(self):
return self.get('Name', locale=True)
def getGenericName(self):
return self.get('GenericName', locale=True)
- def getComment(self):
- return self.get('Comment', locale=True)
def getNoDisplay(self):
return self.get('NoDisplay', type="boolean")
+ def getComment(self):
+ return self.get('Comment', locale=True)
def getIcon(self):
return self.get('Icon', locale=True)
def getHidden(self):
return self.get('Hidden', type="boolean")
- def getFilePattern(self):
- return self.get('FilePattern', type="regex")
+ def getOnlyShowIn(self):
+ return self.get('OnlyShowIn', list=True)
+ def getNotShowIn(self):
+ return self.get('NotShowIn', list=True)
def getTryExec(self):
return self.get('TryExec')
def getExec(self):
@@ -61,41 +64,19 @@ class DesktopEntry(IniFile):
return self.get('Path')
def getTerminal(self):
return self.get('Terminal', type="boolean")
- def getSwallowTitle(self):
- return self.get('SwallowTitle', locale=True)
- def getSwallowExec(self):
- return self.get('SwallowExec')
- def getActions(self):
- return self.get('Actions', list=True)
""" @deprecated, use getMimeTypes instead """
def getMimeType(self):
return self.get('MimeType', list=True, type="regex")
def getMimeTypes(self):
return self.get('MimeType', list=True)
- def getSortOrder(self):
- return self.get('SortOrder', list=True)
- def getDev(self):
- return self.get('Dev')
- def getFSType(self):
- return self.get('FSType')
- def getMountPoint(self):
- return self.get('MountPoint')
- def getReadonly(self):
- return self.get('ReadOnly', type="boolean")
- def getUnmountIcon(self):
- return self.get('UnmountIcon', locale=True)
- def getURL(self):
- return self.get('URL')
def getCategories(self):
return self.get('Categories', list=True)
- def getOnlyShowIn(self):
- return self.get('OnlyShowIn', list=True)
- def getNotShowIn(self):
- return self.get('NotShowIn', list=True)
def getStartupNotify(self):
return self.get('StartupNotify', type="boolean")
def getStartupWMClass(self):
return self.get('StartupWMClass')
+ def getURL(self):
+ return self.get('URL')
# end standard keys
# start kde keys
@@ -107,6 +88,16 @@ class DesktopEntry(IniFile):
return self.get('Keywords', list=True, locale=True)
def getInitialPreference(self):
return self.get('InitialPreference')
+ def getDev(self):
+ return self.get('Dev')
+ def getFSType(self):
+ return self.get('FSType')
+ def getMountPoint(self):
+ return self.get('MountPoint')
+ def getReadonly(self):
+ return self.get('ReadOnly', type="boolean")
+ def getUnmountIcon(self):
+ return self.get('UnmountIcon', locale=True)
# end kde keys
# start deprecated keys
@@ -124,6 +115,18 @@ class DesktopEntry(IniFile):
return self.get('BinaryPattern')
def getMapNotify(self):
return self.get('MapNotify')
+ def getEncoding(self):
+ return self.get('Encoding')
+ def getSwallowTitle(self):
+ return self.get('SwallowTitle', locale=True)
+ def getSwallowExec(self):
+ return self.get('SwallowExec')
+ def getSortOrder(self):
+ return self.get('SortOrder', list=True)
+ def getFilePattern(self):
+ return self.get('FilePattern', type="regex")
+ def getActions(self):
+ return self.get('Actions', list=True)
# end deprecated keys
# desktop entry edit stuff
@@ -132,9 +135,11 @@ class DesktopEntry(IniFile):
type = "Application"
elif os.path.splitext(filename)[1] == ".directory":
type = "Directory"
+ else:
+ raise ParsingError("Unknown extension", filename)
+
self.content = dict()
self.addGroup(self.defaultGroup)
- self.set("Encoding", "UTF-8")
self.set("Type", type)
self.filename = filename
# end desktop entry edit stuff
@@ -157,18 +162,6 @@ class DesktopEntry(IniFile):
except KeyError:
self.errors.append("Key 'Type' is missing")
- # Encoding
- try:
- self.encoding = self.content[self.defaultGroup]["Encoding"]
- except KeyError:
- self.errors.append("Key 'Encoding' is missing")
-
- # Version
- try:
- self.version = self.content[self.defaultGroup]["Version"]
- except KeyError:
- self.warnings.append("Key 'Version' is missing")
-
# Name
try:
self.name = self.content[self.defaultGroup]["Name"]
@@ -189,26 +182,27 @@ class DesktopEntry(IniFile):
def checkKey(self, key, value, group):
# standard keys
if key == "Type":
- if value == "ServiceType" or value == "Service":
+ if value == "ServiceType" or value == "Service" or value == "FSDevice":
self.warnings.append("Type=%s is a KDE extension" % key)
elif value == "MimeType":
self.warnings.append("Type=MimeType is deprecated")
- elif not (value == "Application" or value == "Link" or value == "FSDevice" or value == "Directory"):
- self.errors.append("Value of key 'Type' must be Application, Link, FSDevice or Directory, but is '%s'" % value)
+ elif not (value == "Application" or value == "Link" or value == "Directory"):
+ self.errors.append("Value of key 'Type' must be Application, Link or Directory, but is '%s'" % value)
if self.fileExtension == ".directory" and not value == "Directory":
self.warnings.append("File extension is .directory, but Type is '%s'" % value)
elif self.fileExtension == ".desktop" and value == "Directory":
self.warnings.append("Files with Type=Directory should have the extension .directory")
- elif key == "Version":
- self.checkValue(key, value, type="number")
+ if value == "Application":
+ if not self.content[group].has_key("Exec"):
+ self.warnings.append("Type=Application needs 'Exec' key")
+ if value == "Link":
+ if not self.content[group].has_key("URL"):
+ self.warnings.append("Type=Application needs 'Exec' key")
- elif key == "Encoding":
- if value == "Legacy-Mixed":
- self.errors.append("Encoding=Legacy-Mixed is deprecated and not supported by this parser")
- elif not value == "UTF-8":
- self.errors.append("Value of key 'Encoding' must be UTF-8")
+ elif key == "Version":
+ self.checkValue(key, value)
elif re.match("^Name"+xdg.Locale.regex+"$", key):
pass # locale string
@@ -216,18 +210,25 @@ class DesktopEntry(IniFile):
elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
pass # locale string
+ elif key == "NoDisplay":
+ self.checkValue(key, value, type="boolean")
+
elif re.match("^Comment"+xdg.Locale.regex+"$", key):
pass # locale string
- elif key == "NoDisplay":
- self.checkValue(key, value, type="boolean")
+ elif re.match("^Icon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
elif key == "Hidden":
self.checkValue(key, value, type="boolean")
- elif key == "Terminal":
- self.checkValue(key, value, type="boolean")
- self.checkType(key, "Application")
+ elif key == "OnlyShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
+
+ elif key == "NotShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
elif key == "TryExec":
self.checkValue(key, value)
@@ -241,26 +242,12 @@ class DesktopEntry(IniFile):
self.checkValue(key, value)
self.checkType(key, "Application")
- elif re.match("^Icon"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value)
-
- elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
- self.checkType(key, "Application")
-
- elif key == "SwallowExec":
- self.checkValue(key, value)
- self.checkType(key, "Application")
-
- elif key == "FilePatterns":
- self.checkValue(key, value, type="regex", list=True)
- self.checkType(key, "Application")
-
- elif key == "Actions":
- self.checkValue(key, value, list=True)
+ elif key == "Terminal":
+ self.checkValue(key, value, type="boolean")
self.checkType(key, "Application")
elif key == "MimeType":
- self.checkValue(key, value, type="regex", list=True)
+ self.checkValue(key, value, list=True)
self.checkType(key, "Application")
elif key == "Categories":
@@ -268,14 +255,6 @@ class DesktopEntry(IniFile):
self.checkType(key, "Application")
self.checkCategorie(value)
- elif key == "OnlyShowIn":
- self.checkValue(key, value, list=True)
- self.checkOnlyShowIn(value)
-
- elif key == "NotShowIn":
- self.checkValue(key, value, list=True)
- self.checkOnlyShowIn(value)
-
elif key == "StartupNotify":
self.checkValue(key, value, type="boolean")
self.checkType(key, "Application")
@@ -283,52 +262,57 @@ class DesktopEntry(IniFile):
elif key == "StartupWMClass":
self.checkType(key, "Application")
- elif key == "SortOrder":
- self.checkValue(key, value, list=True)
- self.checkType(key, "Directory")
-
elif key == "URL":
self.checkValue(key, value)
self.checkType(key, "URL")
+ # kde extensions
+ elif key == "ServiceTypes":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "DocPath":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "InitialPreference":
+ self.checkValue(key, value, type="numeric")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
elif key == "Dev":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
elif key == "FSType":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
elif key == "MountPoint":
self.checkValue(key, value)
self.checkType(key, "FSDevice")
-
- elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value)
- self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
elif key == "ReadOnly":
self.checkValue(key, value, type="boolean")
self.checkType(key, "FSDevice")
-
- # kde extensions
- elif key == "ServiceTypes":
- self.checkValue(key, value, list=True)
self.warnings.append("Key '%s' is a KDE extension" % key)
- elif key == "DocPath":
+ elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
self.checkValue(key, value)
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "InitialPreference":
- self.checkValue(key, value, type="number")
+ self.checkType(key, "FSDevice")
self.warnings.append("Key '%s' is a KDE extension" % key)
# deprecated keys
+ elif key == "Encoding":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
@@ -357,6 +341,25 @@ class DesktopEntry(IniFile):
self.checkValue(key, value)
self.warnings.append("Key '%s' is deprecated" % key)
+ elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SwallowExec":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "FilePattern":
+ self.checkValue(key, value, type="regex", list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SortOrder":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Actions":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
# "X-" extensions
elif re.match("^X-[a-zA-Z0-9-]+", key):
pass
@@ -370,14 +373,25 @@ class DesktopEntry(IniFile):
def checkOnlyShowIn(self, value):
values = self.getList(value)
- valid = ["GNOME", "KDE", "ROX", "XFCE", "Old"]
+ valid = ["GNOME", "KDE", "ROX", "XFCE", "Old", "LXDE"]
for item in values:
- if item not in valid:
+ if item not in valid and item[0:2] != "X-":
self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
def checkCategorie(self, value):
values = self.getList(value)
- valid = ["Legacy","Core","Development","Building","Debugger","IDE","GUIDesigner","Profiling","RevisionControl","Translation","Office","Calendar","ContactManagement","Database","Dictionary","Chart","Email","Finance","FlowChart","PDA","ProjectManagement","Presentation","Spreadsheet","WordProcessor","Graphics","2DGraphics","VectorGraphics","RasterGraphics","3DGraphics","Scanning","OCR","Photograph","Viewer","Settings","DesktopSettings","HardwareSettings","PackageManager","Network","Dialup","InstantMessaging","IRCClient","FileTransfer","HamRadio","News","P2P","RemoteAccess","Telephony","WebBrowser","WebDevelopment","AudioVideo","Audio","Midi","Mixer","Sequencer","Tuner","Video","TV","AudioVideoEditing","Player","Recorder","DiscBurning","Game","ActionGame","AdventureGame","ArcadeGame","BoardGame","BlocksGame","CardGame","KidsGame","LogicGame","RolePlaying","Simulation","SportsGame","StrategyGame","Education","Art","Art","Contruction","Music","Languages","Science","Astronomy","Biology","Chemistry","Geology","Math","MedicalSoftware","Physics","Teaching","Amusement","Applet","Archiving","Electronics","Emulator","Engineering","FileManager","Shell","Screensaver","TerminalEmulator","TrayIcon","System","Filesystem","Monitor","Security","Utility","Accessibility","Calculator","Clock","TextEditor","KDE","GNOME","GTK","Qt","Motif","Java","ConsoleOnly"]
+
+ main = ["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Settings", "System", "Utility"]
+ hasmain = False
for item in values:
- if item not in valid:
+ if item in main:
+ hasmain = True
+ if hasmain == False:
+ self.errors.append("Missing main category")
+
+ additional = ["Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", "Translation", "Calendar", "ContactManagement", "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor", "2DGraphics", "VectorGraphics", "3DGraphics", "RasterGraphics", "Scanning", "OCR", "Photography", "Publishing", "Viewer", "TextTools", "DesktopSettings", "HardwareSettings", "Printing", "PackageManager", "Dialup", "InstantMessaging", "Chat", "IRCClient", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", "Telephony", "TelephonyTools", "VideoConference", "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Simulation", "SportsGame", "StrategyGame", "Art", "Construction", "Music", "Languages", "Science", "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", "ComputerScience", "DataVisualization", "Economy", "Electricity", "Geography", "Geology", "Geoscience", "History", "ImageProcessing", "Literature", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", "Robotics", "Sports", "ParallelComputing", "Amusement", "Archiving", "Compression", "Electronics", "Emulator", "Engineering", "FileTools", "FileManager", "TerminalEmulator", "Filesystem", "Monitor", "Security", "Accessibility", "Calculator", "Clock", "TextEditor", "Documentation", "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", "Java", "ConsoleOnly", "Screensaver", "TrayIcon", "Applet", "Shell"]
+
+ for item in values:
+ if item not in additional + main and item[0:2] != "X-":
self.errors.append("'%s' is not a registered Category" % item);
+
diff --git a/xdg/IniFile.py b/xdg/IniFile.py
index 161e885..f3f08c7 100644
--- a/xdg/IniFile.py
+++ b/xdg/IniFile.py
@@ -2,7 +2,7 @@
Base Class for DesktopEntry, IconTheme and IconData
"""
-import re, os.path, codecs
+import re, os, stat, codecs
from Exceptions import *
import xdg.Locale
@@ -22,7 +22,7 @@ class IniFile:
def __cmp__(self, other):
return cmp(self.content, other.content)
- def parse(self, filename, headers):
+ def parse(self, filename, headers=None):
# for performance reasons
content = self.content
@@ -64,7 +64,7 @@ class IniFile:
else:
content[currentGroup][key] = value
except (IndexError, UnboundLocalError):
- raise ParsingError("[%s]-Header missing" % headers[0], filename)
+ raise ParsingError("Parsing error on key, group missing", filename)
fd.close()
@@ -72,12 +72,13 @@ class IniFile:
self.tainted = False
# check header
- for header in headers:
- if content.has_key(header):
- self.defaultGroup = header
- break
- else:
- raise ParsingError("[%s]-Header missing" % headers[0], filename)
+ if headers:
+ for header in headers:
+ if content.has_key(header):
+ self.defaultGroup = header
+ break
+ else:
+ raise ParsingError("[%s]-Header missing" % headers[0], filename)
# start stuff to access the keys
def get(self, key, group=None, locale=False, type="string", list=False):
@@ -224,7 +225,7 @@ class IniFile:
code = self.checkString(value)
elif type == "boolean":
code = self.checkBoolean(value)
- elif type == "number":
+ elif type == "numeric":
code = self.checkNumber(value)
elif type == "integer":
code = self.checkInteger(value)
@@ -278,7 +279,7 @@ class IniFile:
return 1
# write support
- def write(self, filename=None):
+ def write(self, filename=None, trusted=False):
if not filename and not self.filename:
raise ParsingError("File not found", "")
@@ -291,6 +292,14 @@ class IniFile:
os.makedirs(os.path.dirname(filename))
fp = codecs.open(filename, 'w')
+
+ # An executable bit signifies that the desktop file is
+ # trusted, but then the file can be executed. Add hashbang to
+ # make sure that the file is opened by something that
+ # understands desktop files.
+ if trusted:
+ fp.write("#!/usr/bin/env xdg-open\n")
+
if self.defaultGroup:
fp.write("[%s]\n" % self.defaultGroup)
for (key, value) in self.content[self.defaultGroup].items():
@@ -302,6 +311,13 @@ class IniFile:
for (key, value) in group.items():
fp.write("%s=%s\n" % (key, value))
fp.write("\n")
+
+ # Add executable bits to the file to show that it's trusted.
+ if trusted:
+ oldmode = os.stat(filename).st_mode
+ mode = oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
+ os.chmod(filename, mode)
+
self.tainted = False
def set(self, key, value, group=None, locale=False):
diff --git a/xdg/Mime.py b/xdg/Mime.py
index 66eefaf..04fe0d2 100644
--- a/xdg/Mime.py
+++ b/xdg/Mime.py
@@ -284,7 +284,7 @@ class MagicDB:
if pri<min_pri:
break
for type in self.types[pri]:
- m=type.match(buf)
+ m=type.match(data)
if m:
return m