diff --git a/src/Mod/Start/StartPage/StartPage.html b/src/Mod/Start/StartPage/StartPage.html
index 36a3bf2aa0..9227550771 100644
--- a/src/Mod/Start/StartPage/StartPage.html
+++ b/src/Mod/Start/StartPage/StartPage.html
@@ -1,9 +1,10 @@
-
+
+
T_TITLE
-
-
+
+
@@ -12,7 +13,7 @@
VERSIONSTRING
-
+
@@ -57,25 +58,25 @@
T_GENERALDOCUMENTATION
T_DESCR_USERHUB
T_DESCR_POWERHUB
T_DESCR_DEVHUB
T_DESCR_MANUAL
diff --git a/src/Mod/Start/StartPage/StartPage.py b/src/Mod/Start/StartPage/StartPage.py
index a19c46949c..20589363bf 100644
--- a/src/Mod/Start/StartPage/StartPage.py
+++ b/src/Mod/Start/StartPage/StartPage.py
@@ -148,19 +148,19 @@ def getInfo(filename):
if files[0] == "Document.xml":
doc = str(zfile.read(files[0]))
doc = doc.replace("\n"," ")
- r = re.findall("Property name=\"CreatedBy.*?String value=\"(.*?)\"\/>",doc)
+ r = re.findall("Property name=\"CreatedBy.*?String value=\"(.*?)\"/>",doc)
if r:
author = r[0]
# remove email if present in author field
if "<" in author:
author = author.split("<")[0].strip()
- r = re.findall("Property name=\"Company.*?String value=\"(.*?)\"\/>",doc)
+ r = re.findall("Property name=\"Company.*?String value=\"(.*?)\"/>",doc)
if r:
company = r[0]
- r = re.findall("Property name=\"License.*?String value=\"(.*?)\"\/>",doc)
+ r = re.findall("Property name=\"License.*?String value=\"(.*?)\"/>",doc)
if r:
lic = r[0]
- r = re.findall("Property name=\"Comment.*?String value=\"(.*?)\"\/>",doc)
+ r = re.findall("Property name=\"Comment.*?String value=\"(.*?)\"/>",doc)
if r:
descr = r[0]
if "thumbnails/Thumbnail.png" in files:
@@ -247,16 +247,16 @@ def buildCard(filename,method,arg=None):
if finfo[5]:
infostring += "\n\n" + encode(finfo[5])
if size:
- result += ''
result += ''
- result += '
'
+ result += ''
+ result += '
'
result += ''
result += '
'+encode(basename)+'
'
result += '
'+encode(author)+'
'
result += '
'+size+'
'
result += '
'
- result += ''
result += ''
+ result += ''
return result
@@ -298,6 +298,10 @@ def handle():
HTML = HTML.replace("CSS",CSS)
HTML = encode(HTML)
+ # set the language
+
+ HTML = HTML.replace("BCP47_LANGUAGE",QtCore.QLocale().bcp47Name())
+
# get the stylesheet if we are using one
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("UseStyleSheet",False):
@@ -370,17 +374,17 @@ def handle():
rfcount = rf.GetInt("RecentFiles",0)
SECTION_RECENTFILES = encode(""+TranslationTexts.T_RECENTFILES+"
")
SECTION_RECENTFILES += ""
- SECTION_RECENTFILES += ''
SECTION_RECENTFILES += '- '
+ SECTION_RECENTFILES += ''
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("NewFileGradient",False):
- SECTION_RECENTFILES += '
'
+ SECTION_RECENTFILES += '
'
else:
- SECTION_RECENTFILES += '
'
+ SECTION_RECENTFILES += '
'
SECTION_RECENTFILES += ''
SECTION_RECENTFILES += '
'+encode(TranslationTexts.T_CREATENEW)+'
'
SECTION_RECENTFILES += ''
- SECTION_RECENTFILES += ' '
SECTION_RECENTFILES += ''
+ SECTION_RECENTFILES += ''
for i in range(rfcount):
filename = rf.GetString("MRU%d" % (i))
SECTION_RECENTFILES += encode(buildCard(filename,method="LoadMRU.py?MRU=",arg=str(i)))
@@ -424,14 +428,12 @@ def handle():
# build IMAGE_SRC paths
- HTML = HTML.replace("IMAGE_SRC_USERHUB",'file:///'+os.path.join(resources_dir, 'images/userhub.png'))
- HTML = HTML.replace("IMAGE_SRC_POWERHUB",'file:///'+os.path.join(resources_dir, 'images/poweruserhub.png'))
- HTML = HTML.replace("IMAGE_SRC_DEVHUB",'file:///'+os.path.join(resources_dir, 'images/developerhub.png'))
- HTML = HTML.replace("IMAGE_SRC_MANUAL",'file:///'+os.path.join(resources_dir, 'images/manual.png'))
- HTML = HTML.replace("IMAGE_SRC_SETTINGS",'file:///'+os.path.join(resources_dir, 'images/settings.png'))
- imagepath= 'file:///'+os.path.join(resources_dir, 'images/installed.png')
- imagepath = imagepath.replace('\\','/') # replace Windows backslash with slash to make the path javascript compatible
- HTML = HTML.replace("IMAGE_SRC_INSTALLED",imagepath)
+ HTML = HTML.replace("IMAGE_SRC_USERHUB",'file:///'+os.path.join(resources_dir, 'images/userhub.png').replace('\\','/'))
+ HTML = HTML.replace("IMAGE_SRC_POWERHUB",'file:///'+os.path.join(resources_dir, 'images/poweruserhub.png').replace('\\','/'))
+ HTML = HTML.replace("IMAGE_SRC_DEVHUB",'file:///'+os.path.join(resources_dir, 'images/developerhub.png').replace('\\','/'))
+ HTML = HTML.replace("IMAGE_SRC_MANUAL",'file:///'+os.path.join(resources_dir, 'images/manual.png').replace('\\','/'))
+ HTML = HTML.replace("IMAGE_SRC_SETTINGS",'file:///'+os.path.join(resources_dir, 'images/settings.png').replace('\\','/'))
+ HTML = HTML.replace("IMAGE_SRC_INSTALLED",'file:///'+os.path.join(resources_dir, 'images/installed.png').replace('\\','/'))
# build UL_WORKBENCHES
@@ -481,7 +483,7 @@ def handle():
xpm = w.Icon
if "XPM" in xpm:
xpm = xpm.replace("\n ","\n") # some XPMs have some indent that QT doesn't like
- r = [s[:-1].strip('"') for s in re.findall("(?s)\{(.*?)\};",xpm)[0].split("\n")[1:]]
+ r = [s[:-1].strip('"') for s in re.findall("(?s){(.*?)};",xpm)[0].split("\n")[1:]]
p = QtGui.QPixmap(r)
p = p.scaled(24,24)
img = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1]
@@ -492,7 +494,7 @@ def handle():
img = os.path.join(resources_dir,"images/freecad.png")
iconbank[wb] = img
UL_WORKBENCHES += '- '
- UL_WORKBENCHES += '
'
+ UL_WORKBENCHES += '
'
UL_WORKBENCHES += ''+wn.replace("ReverseEngineering","ReverseEng")+''
UL_WORKBENCHES += ' '
UL_WORKBENCHES += '
'
diff --git a/src/Mod/Start/TestStart/TestStartPage.py b/src/Mod/Start/TestStart/TestStartPage.py
index 9e0740a658..7cc1da5761 100644
--- a/src/Mod/Start/TestStart/TestStartPage.py
+++ b/src/Mod/Start/TestStart/TestStartPage.py
@@ -24,7 +24,7 @@ import unittest
import FreeCAD
import Start
from StartPage import StartPage
-from html.parser import HTMLParser
+import re
class TestStartPage(unittest.TestCase):
"""Basic validation of the generated Start page."""
@@ -35,6 +35,7 @@ class TestStartPage(unittest.TestCase):
def setUp(self):
pass
+
def test_all_css_placeholders_removed(self):
"""Check to see if all of the CSS placeholders have been replaced."""
placeholders = ["BACKGROUND","BGTCOLOR","FONTFAMILY","FONTSIZE","LINKCOLOR",
@@ -44,6 +45,7 @@ class TestStartPage(unittest.TestCase):
for placeholder in placeholders:
self.assertNotIn (placeholder, page, "{} was not removed from the CSS".format(placeholder))
+
def test_all_js_placeholders_removed(self):
"""Check to see if all of the JavaScript placeholders have been replaced."""
placeholders = ["IMAGE_SRC_INSTALLED"]
@@ -51,6 +53,7 @@ class TestStartPage(unittest.TestCase):
for placeholder in placeholders:
self.assertNotIn (placeholder, page, "{} was not removed from the JS".format(placeholder))
+
def test_all_html_placeholders_removed(self):
"""Check to see if all of the HTML placeholders have been replaced."""
placeholders = ["T_TITLE","VERSIONSTRING","T_DOCUMENTS","T_HELP","T_ACTIVITY",
@@ -69,4 +72,97 @@ class TestStartPage(unittest.TestCase):
page = StartPage.handle()
for placeholder in placeholders:
self.assertNotIn (placeholder, page, "{} was not removed from the HTML".format(placeholder))
-
\ No newline at end of file
+
+
+ def test_files_do_not_contain_backslashes(self):
+ # This would be caught by the W3C validator if we didn't sanitize the filenames before sending them.
+ page = StartPage.handle()
+ fileRE = re.compile(r'"file:///(.*?)"')
+ results = fileRE.findall(string=page)
+
+ badFilenames = []
+ for result in results:
+ if result.find("\\") != -1:
+ badFilenames.append(result)
+
+ if len(badFilenames) > 0:
+ self.fail("The following filenames contain backslashes, which is prohibited in HTML: {}".format(badFilenames))
+
+
+ def test_html_validates(self):
+ # Send the generated html to the W3C validator for analysis (removing potentially-sensitive data first)
+ import urllib.request
+ import os
+ import json
+ page = self.sanitize(StartPage.handle()) # Remove potentially sensitive data
+
+ # For debugging, if you want to ensure that the sanitization worked correctly:
+ # from pathlib import Path
+ # home = str(Path.home())
+ # f=open(home+"/test.html", "w")
+ # f.write(page)
+ # f.close()
+
+ validation_url = "https://validator.w3.org/nu/?out=json"
+ data = page.encode('utf-8') # data should be bytes
+ req = urllib.request.Request(validation_url, data)
+ req.add_header("Content-type","text/html; charset=utf-8")
+ errorCount = 0
+ warningCount = 0
+ infoCount = 0
+ validationResultString = ""
+ try:
+ with urllib.request.urlopen (req) as response:
+ text = response.read()
+
+ responseJSON = json.loads(text)
+
+ for message in responseJSON["messages"]:
+ if "type" in message:
+ if message["type"] == "info":
+ if "subtype" in message:
+ if message["subtype"] == "warning":
+ warningCount += 1
+ validationResultString += "WARNING: {}\n".format(ascii(message["message"]))
+ else:
+ infoCount += 1
+ validationResultString += "INFO: {}\n".format(ascii(message["message"]))
+ elif message["type"] == "error":
+ errorCount += 1
+ validationResultString += "ERROR: {}\n".format(ascii(message["message"]))
+ elif message["type"] == "non-document-error":
+ FreeCAD.Console.PrintWarning("W3C validator returned a non-document error:\n {}".format(message))
+ return
+
+ except urllib.error.HTTPError as e:
+ FreeCAD.Console.PrintWarning("W3C validator returned response code {}".format(e.code))
+
+ except urllib.error.URLError:
+ FreeCAD.Console.PrintWarning("Could not communicate with W3C validator")
+
+ if errorCount > 0 or warningCount > 0:
+ StartPage.exportTestFile()
+ FreeCAD.Console.PrintWarning("HTML validation failed: Start page source written to your home directory for analysis.")
+ self.fail("W3C Validator analysis shows the Start page has {} errors and {} warnings:\n\n{}".format(errorCount, warningCount, validationResultString))
+ elif infoCount > 0:
+ FreeCAD.Console.PrintWarning("The Start page is valid HTML, but the W3C sent back {} informative messages:\n{}.".format(infoCount,validationResultString))
+
+ def sanitize (self, html):
+
+ # Anonymize all local filenames
+ fileRE = re.compile(r'"file:///.*?"')
+ html = fileRE.sub(repl=r'"file:///A/B/C"', string=html)
+
+ # Anonymize titles, which are used for mouseover text and might contain document information
+ titleRE = re.compile(r'title="[\s\S]*?"') # Some titles have newlines in them
+ html = titleRE.sub(repl=r'title="Y"', string=html)
+
+ # Anonymize the document names, which we display in tags
+ h4RE = re.compile(r'.*?
')
+ html = h4RE.sub(repl=r'Z
', string=html)
+
+ # Remove any simple single-line paragraphs, which might contain document author information, file size information, etc.
+ pRE = re.compile(r'
[^<]*?
')
+ html = pRE.sub(repl=r'X
', string=html)
+
+ return html