on xenial help sign in
Better attempt at sanitization

For colors, only accept numbers, chars and # For URIs pass them through encodeURI on compile time and decodeURI on run time

commitf40fe1e32a6a2a920f2778965271c2a68c55bd71 authorAlbert Astals Cid <albert.astals@canonical.com> dateMon 25 Jan 2016, 17:49 (11 years ago) parentc0409a8
8 files changed+414−48
plugins/Dash/CardCreator.js+27−23 tests/plugins/Dash/cardcreator/10.res+142 tests/plugins/Dash/cardcreator/10.tst+2−1 tests/plugins/Dash/cardcreator/11.res+217 tests/plugins/Dash/cardcreator/11.tst+3 tests/plugins/Dash/cardcreator/3.res+1−1 tests/plugins/Dash/cardcreator/6.res+1−1 tests/plugins/Dash/cardcreatortest.cpp+21−22
plugins/Dash/CardCreator.js+27−23
@@ -46,7 +46,7 @@ var kBackgroundLoaderCode = 'Loader {\n\
4646 objectName: "backgroundImage"; \n\
4747 source: { \n\
4848 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49− else return "%1"; \n\
49+ else return %1; \n\
5050 } \n\
5151 } \n\
5252 function getColor(index) { \n\
@@ -373,27 +373,21 @@ var kAudioProgressBarCode = 'CardAudioProgress { \n\
373373 color: %3; \n\
374374 }';
375375
376−function evil_param(object) {
377− for (var x in object) {
378− if (typeof object[x] === "object" && evil_param(object[x]))
379− return true;
380−
381− if (typeof object[x] === "string" && object[x].match(/"(?:[^"\\]|\\.)*"/) != null)
382− return true;
376+function sanitizeColor(colorString) {
377+ if (colorString !== undefined) {
378+ if (colorString.match(/^[#a-z0-9]*$/i) === null) {
379+ // This is not the perfect regexp for color
380+ // but what we're trying to do here is just protect
381+ // against injection so it's ok
382+ return "";
383+ }
383384 }
384−
385− return false;
385+ return colorString;
386386}
387387
388388function cardString(template, components) {
389389 var code;
390390
391− if (evil_param(template))
392− return "";
393−
394− if (evil_param(components))
395− return "";
396−
397391 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
398392
399393 code = 'AbstractButton { \n\
@@ -442,15 +436,23 @@ function cardString(template, components) {
442436 }
443437
444438 if (hasBackground) {
445− var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
439+ var templateCardBackground;
440+ if (template && typeof template["card-background"] === "string") {
441+ templateCardBackground = 'decodeURI("' + encodeURI(template["card-background"]) + '")';
442+ } else {
443+ templateCardBackground = '""';
444+ }
445+
446446 var backgroundElements0;
447447 var backgroundElements1;
448448 if (template && typeof template["card-background"] === "object" && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
449− if (template["card-background"]["elements"][0] !== undefined) {
450− backgroundElements0 = '"%1"'.arg(template["card-background"]["elements"][0]);
449+ var element0 = sanitizeColor(template["card-background"]["elements"][0]);
450+ var element1 = sanitizeColor(template["card-background"]["elements"][1]);
451+ if (element0 !== undefined) {
452+ backgroundElements0 = '"%1"'.arg(element0);
451453 }
452− if (template["card-background"]["elements"][1] !== undefined) {
453− backgroundElements1 = '"%1"'.arg(template["card-background"]["elements"][1]);
454+ if (element1 !== undefined) {
455+ backgroundElements1 = '"%1"'.arg(element1);
454456 }
455457 }
456458 code += kBackgroundLoaderCode.arg(templateCardBackground).arg(backgroundElements0).arg(backgroundElements1);
@@ -479,9 +481,10 @@ function cardString(template, components) {
479481 }
480482
481483 var fallback = components["art"] && components["art"]["fallback"] || "";
484+ fallback = encodeURI(fallback);
482485 var fallbackCode = "";
483486 if (fallback !== "") {
484− fallbackCode += 'onStatusChanged: if (status === Image.Error) source = "%1";'.arg(fallback);
487+ fallbackCode += 'onStatusChanged: if (status === Image.Error) source = decodeURI("%1");'.arg(fallback);
485488 }
486489 code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode).arg(fallbackCode);
487490 } else {
@@ -576,9 +579,10 @@ function cardString(template, components) {
576579
577580 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
578581 var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
582+ fallback = encodeURI(fallback);
579583 var fallbackCode = "";
580584 if (fallback !== "") {
581− fallbackCode += 'onStatusChanged: if (status === Image.Error) source = "%1";'.arg(fallback);
585+ fallbackCode += 'onStatusChanged: if (status === Image.Error) source = decodeURI("%1");'.arg(fallback);
582586 }
583587 mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible).arg(fallbackCode);
584588 }
tests/plugins/Dash/cardcreator/10.resnew file+142
@@ -0,0 +1,142 @@
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property string artShapeStyle: "inset";
6+ property string backgroundShapeStyle: "inset";
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int titleAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+ enabled: true;
17+
18+Loader {
19+ id: backgroundLoader;
20+ objectName: "backgroundLoader";
21+ anchors.fill: parent;
22+ asynchronous: root.asynchronous;
23+ visible: status == Loader.Ready;
24+ sourceComponent: UbuntuShape {
25+ objectName: "background";
26+ radius: "medium";
27+ aspect: {
28+ switch (root.backgroundShapeStyle) {
29+ case "inset": return UbuntuShape.Inset;
30+ case "shadow": return UbuntuShape.DropShadow;
31+ default:
32+ case "flat": return UbuntuShape.Flat;
33+ }
34+ }
35+ backgroundColor: getColor(0) || "white";
36+ secondaryBackgroundColor: getColor(1) || backgroundColor;
37+ backgroundMode: UbuntuShape.VerticalGradient;
38+ anchors.fill: parent;
39+ source: backgroundImage.source ? backgroundImage : null;
40+ property real luminance: Style.luminance(backgroundColor);
41+ property Image backgroundImage: Image {
42+ objectName: "backgroundImage";
43+ source: {
44+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45+ else return "";
46+ }
47+ }
48+ function getColor(index) {
49+ if (cardData && typeof cardData["background"] === "object"
50+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
51+ return cardData["background"]["elements"][index];
52+ } else return index === 0 ? "" : "";
53+ }
54+ }
55+ }
56+readonly property size artShapeSize: Qt.size(-1, -1);
57+readonly property int headerHeight: row.height;
58+Row {
59+ id: row;
60+ objectName: "outerRow";
61+ property real margins: units.gu(1);
62+ spacing: margins;
63+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
64+ anchors { top: parent.top;
65+ topMargin: units.gu(1);
66+left: parent.left;
67+ }
68+ anchors.right: parent.right;
69+ anchors.margins: margins;
70+ anchors.rightMargin: 0;
71+ data: [
72+ CroppedImageMinimumSourceSize {
73+ id: mascotImage;
74+ objectName: "mascotImage";
75+ anchors { verticalCenter: parent.verticalCenter; }
76+ source: cardData && cardData["mascot"] || "";
77+ width: units.gu(6);
78+ height: units.gu(5.625);
79+ horizontalAlignment: Image.AlignHCenter;
80+ verticalAlignment: Image.AlignVCenter;
81+ visible: showHeader;
82+
83+ }
84+,Item {
85+ id: headerTitleContainer;
86+ anchors { verticalCenter: parent.verticalCenter; }
87+ width: parent.width - x;
88+ implicitHeight: titleLabel.height + subtitleLabel.height;
89+ data: [
90+ Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+rightMargin: units.gu(1);
95+left: parent.left;
96+ top: parent.top; }
97+ elide: Text.ElideRight;
98+ fontSize: "small";
99+ wrapMode: Text.Wrap;
100+ maximumLineCount: 2;
101+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
102+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
103+ visible: showHeader ;
104+ width: undefined;
105+ text: root.title;
106+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
107+ horizontalAlignment: root.titleAlignment;
108+ }
109+,Label {
110+ id: subtitleLabel;
111+ objectName: "subtitleLabel";
112+ anchors { right: parent.right;
113+ left: parent.left;
114+rightMargin: units.gu(1);
115+top: titleLabel.bottom;
116+ }
117+ anchors.topMargin: units.dp(2);
118+ elide: Text.ElideRight;
119+ maximumLineCount: 1;
120+ fontSize: "x-small";
121+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
122+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
123+ visible: titleLabel.visible && titleLabel.text;
124+ text: cardData && cardData["subtitle"] || "";
125+ font.weight: Font.Light;
126+ }
127+
128+ ]
129+ }
130+
131+ ]
132+ }
133+UbuntuShape {
134+ id: touchdown;
135+ objectName: "touchdown";
136+ anchors { fill: backgroundLoader }
137+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
138+ radius: "medium";
139+ borderSource: "radius_pressed.sci"
140+ }
141+implicitHeight: row.y + row.height + units.gu(1);
142+}
tests/plugins/Dash/cardcreator/10.tst+2−1
@@ -1,2 +1,3 @@
1−template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2, "card-background": { "type": "color", "elements": [ "\" : \"\";} Item { } function moo() { return true ? \"" ] } }
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2, "card-background": { "type": "color", "elements": [ "\\", ": 3; } Item { } function moo () { \"" ] } }
22components: {"art":{"aspect-ratio":1},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{}}
3+result: 10.res
tests/plugins/Dash/cardcreator/11.resnew file+217
@@ -0,0 +1,217 @@
1+AbstractButton {
2+ id: root;
3+ property var components;
4+ property var cardData;
5+ property string artShapeStyle: "inset";
6+ property string backgroundShapeStyle: "inset";
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int titleAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+ enabled: true;
17+
18+Loader {
19+ id: backgroundLoader;
20+ objectName: "backgroundLoader";
21+ anchors.fill: parent;
22+ asynchronous: root.asynchronous;
23+ visible: status == Loader.Ready;
24+ sourceComponent: UbuntuShape {
25+ objectName: "background";
26+ radius: "medium";
27+ aspect: {
28+ switch (root.backgroundShapeStyle) {
29+ case "inset": return UbuntuShape.Inset;
30+ case "shadow": return UbuntuShape.DropShadow;
31+ default:
32+ case "flat": return UbuntuShape.Flat;
33+ }
34+ }
35+ backgroundColor: getColor(0) || "white";
36+ secondaryBackgroundColor: getColor(1) || backgroundColor;
37+ backgroundMode: UbuntuShape.VerticalGradient;
38+ anchors.fill: parent;
39+ source: backgroundImage.source ? backgroundImage : null;
40+ property real luminance: Style.luminance(backgroundColor);
41+ property Image backgroundImage: Image {
42+ objectName: "backgroundImage";
43+ source: {
44+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45+ else return "";
46+ }
47+ }
48+ function getColor(index) {
49+ if (cardData && typeof cardData["background"] === "object"
50+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
51+ return cardData["background"]["elements"][index];
52+ } else return index === 0 ? "#E9E9E9" : undefined;
53+ }
54+ }
55+ }
56+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
57+Item {
58+ id: artShapeHolder;
59+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
60+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
61+ anchors { horizontalCenter: parent.horizontalCenter; }
62+ Loader {
63+ id: artShapeLoader;
64+ objectName: "artShapeLoader";
65+ active: cardData && cardData["art"] || false;
66+ asynchronous: root.asynchronous;
67+ visible: status == Loader.Ready;
68+ sourceComponent: Item {
69+ id: artShape;
70+ objectName: "artShape";
71+ readonly property bool doShapeItem: components["art"]["conciergeMode"] !== true;
72+ visible: image.status == Image.Ready;
73+ readonly property alias image: artImage;
74+ ShaderEffectSource {
75+ id: artShapeSource;
76+ sourceItem: artImage;
77+ anchors.centerIn: parent;
78+ width: 1;
79+ height: 1;
80+ hideSource: doShapeItem;
81+ }
82+ Loader {
83+ anchors.fill: parent;
84+ visible: artShape.doShapeItem;
85+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
86+ Component {
87+ id: artShapeShapeComponent;
88+ UbuntuShape {
89+ source: artShapeSource;
90+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
91+ radius: "medium";
92+ aspect: {
93+ switch (root.artShapeStyle) {
94+ case "inset": return UbuntuShape.Inset;
95+ case "shadow": return UbuntuShape.DropShadow;
96+ default:
97+ case "flat": return UbuntuShape.Flat;
98+ }
99+ }
100+ }
101+ }
102+ Component {
103+ id: artShapeIconComponent;
104+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
105+ }
106+ }
107+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
108+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
109+ Component.onCompleted: { updateWidthHeightBindings(); }
110+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
111+ function updateWidthHeightBindings() {
112+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
113+ width = root.fixedArtShapeSize.width;
114+ height = root.fixedArtShapeSize.height;
115+ } else {
116+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
117+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
118+ }
119+ }
120+ CroppedImageMinimumSourceSize {
121+ id: artImage;
122+ objectName: "artImage";
123+ source: cardData && cardData["art"] || "";
124+ asynchronous: root.asynchronous;
125+ width: root.width;
126+ height: width / artShape.aspect;
127+ onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
128+ }
129+ }
130+ }
131+ }
132+readonly property int headerHeight: row.height;
133+Row {
134+ id: row;
135+ objectName: "outerRow";
136+ property real margins: units.gu(1);
137+ spacing: margins;
138+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
139+ anchors { top: artShapeHolder.bottom;
140+ topMargin: units.gu(1);
141+left: parent.left;
142+ }
143+ anchors.right: parent.right;
144+ anchors.margins: margins;
145+ anchors.rightMargin: 0;
146+ data: [
147+ CroppedImageMinimumSourceSize {
148+ id: mascotImage;
149+ objectName: "mascotImage";
150+ anchors { verticalCenter: parent.verticalCenter; }
151+ source: cardData && cardData["mascot"] || "";
152+ width: units.gu(6);
153+ height: units.gu(5.625);
154+ horizontalAlignment: Image.AlignHCenter;
155+ verticalAlignment: Image.AlignVCenter;
156+ visible: showHeader;
157+ onStatusChanged: if (status === Image.Error) source = decodeURI("%22");
158+ }
159+,Item {
160+ id: headerTitleContainer;
161+ anchors { verticalCenter: parent.verticalCenter; }
162+ width: parent.width - x;
163+ implicitHeight: titleLabel.height + subtitleLabel.height;
164+ data: [
165+ Label {
166+ id: titleLabel;
167+ objectName: "titleLabel";
168+ anchors { right: parent.right;
169+rightMargin: units.gu(1);
170+left: parent.left;
171+ top: parent.top; }
172+ elide: Text.ElideRight;
173+ fontSize: "small";
174+ wrapMode: Text.Wrap;
175+ maximumLineCount: 2;
176+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
177+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
178+ visible: showHeader ;
179+ width: undefined;
180+ text: root.title;
181+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
182+ horizontalAlignment: root.titleAlignment;
183+ }
184+,Label {
185+ id: subtitleLabel;
186+ objectName: "subtitleLabel";
187+ anchors { right: parent.right;
188+ left: parent.left;
189+rightMargin: units.gu(1);
190+top: titleLabel.bottom;
191+ }
192+ anchors.topMargin: units.dp(2);
193+ elide: Text.ElideRight;
194+ maximumLineCount: 1;
195+ fontSize: "x-small";
196+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
197+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
198+ visible: titleLabel.visible && titleLabel.text;
199+ text: cardData && cardData["subtitle"] || "";
200+ font.weight: Font.Light;
201+ }
202+
203+ ]
204+ }
205+
206+ ]
207+ }
208+UbuntuShape {
209+ id: touchdown;
210+ objectName: "touchdown";
211+ anchors { fill: backgroundLoader }
212+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
213+ radius: "medium";
214+ borderSource: "radius_pressed.sci"
215+ }
216+implicitHeight: row.y + row.height + units.gu(1);
217+}
tests/plugins/Dash/cardcreator/11.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2 }
2+components: {"art":{"aspect-ratio":1,"field":"art","fallback":"\\"},"background":{"field":"background"},"mascot":{"field":"icon","fallback":"\""},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{}}
3+result: 11.res
tests/plugins/Dash/cardcreator/3.res+1−1
@@ -86,7 +86,7 @@ Item {
8686 asynchronous: root.asynchronous;
8787 width: root.width;
8888 height: width / artShape.aspect;
89− onStatusChanged: if (status === Image.Error) source = "IHAVE\"ESCAPED\"QUOTES\"";
89+ onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
9090 }
9191 }
9292 }
tests/plugins/Dash/cardcreator/6.res+1−1
@@ -42,7 +42,7 @@ Loader {
4242 objectName: "backgroundImage";
4343 source: {
4444 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
45− else return "http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png";
45+ else return decodeURI("http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png");
4646 }
4747 }
4848 function getColor(index) {
tests/plugins/Dash/cardcreatortest.cpp+21−22
@@ -63,6 +63,7 @@ private Q_SLOTS:
6363
6464 QVERIFY(lines[0].startsWith(templateString));
6565 QVERIFY(lines[1].startsWith(componentsString));
66+ QVERIFY(lines[2].startsWith(resultString));
6667
6768 const QString templateJSON = lines[0].mid(templateString.length());
6869 const QString componentsJSON = lines[1].mid(componentsString.length());
@@ -71,32 +72,30 @@ private Q_SLOTS:
7172 QVariant cardStringResult;
7273 QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
7374
74− if (!resultFileName.isEmpty()) {
75− QFile testResultFile(testDirPath + resultFileName);
76− QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77− QTextStream ts2(&testResultFile);
78−
79− // Record failed results to /tmp
80− const QString executedResult = cardStringResult.toString();
81− QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82− tmpFile.open();
83− tmpFile.setAutoRemove(false);
84− tmpFile.write(executedResult.toUtf8().constData());
85−
86− // Line by line comparison
87− const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
88− const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
89− for (int i = 0; i < expectedLines.size(); ++i) {
90− QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
91− }
92−
93− // Remove the result if it passed
94− tmpFile.setAutoRemove(true);
75+ QFile testResultFile(testDirPath + resultFileName);
76+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77+ QTextStream ts2(&testResultFile);
78+
79+ // Record failed results to /tmp
80+ const QString executedResult = cardStringResult.toString();
81+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82+ tmpFile.open();
83+ tmpFile.setAutoRemove(false);
84+ tmpFile.write(executedResult.toUtf8().constData());
85+
86+ // Line by line comparison
87+ const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
88+ const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
89+ for (int i = 0; i < expectedLines.size(); ++i) {
90+ QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
9591 }
9692
93+ // Remove the result if it passed
94+ tmpFile.setAutoRemove(true);
95+
9796 QVariant createCardComponentResult;
9897 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
99− QCOMPARE(createCardComponentResult.toBool(), !resultFileName.isEmpty());
98+ QVERIFY(createCardComponentResult.toBool());
10099 }
101100 }
102101