Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtsvg/src/svg/css/qsvgcssproperties.cpp
Line
Count
Source
1
// Copyright (C) 2025 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:data-parser
4
5
#include "qsvgcssproperties_p.h"
6
#include <QtGui/private/qguisvg_p.h>
7
8
QT_BEGIN_NAMESPACE
9
10
using namespace Qt::Literals::StringLiterals;
11
using namespace QSvgCssValues;
12
13
namespace {
14
15
int parseCssClockValue(QStringView str, bool *ok)
16
0
{
17
0
    int res = 0;
18
0
    int ms = 1000;
19
0
    str = str.trimmed();
20
0
    if (str.endsWith("ms"_L1)) {
21
0
        str.chop(2);
22
0
        ms = 1;
23
0
    } else if (str.endsWith("s"_L1)) {
24
0
        str.chop(1);
25
0
    } else {
26
0
        if (ok)
27
0
            *ok = false;
28
0
        return res;
29
0
    }
30
0
    double val = ms * str.toDouble(ok);
31
0
    if (ok) {
32
0
        if (val > std::numeric_limits<int>::min() && val < std::numeric_limits<int>::max())
33
0
            res = static_cast<int>(val);
34
0
        else
35
0
            *ok = false;
36
0
    }
37
0
    return res;
38
0
}
39
40
0
bool isTimeValue(QStringView str) {
41
0
    if (str.endsWith("ms"_L1))
42
0
        str.chop(2);
43
0
    else if (str.endsWith("s"_L1))
44
0
        str.chop(1);
45
0
    else
46
0
        return false;
47
48
0
    bool ok;
49
0
    Q_UNUSED(str.toDouble(&ok))
50
0
    return ok;
51
0
}
52
53
0
bool isIteration(QStringView str) {
54
0
    if (str == "infinite"_L1)
55
0
        return true;
56
0
    bool ok;
57
0
    Q_UNUSED(str.toDouble(&ok))
58
0
    return ok;
59
0
}
60
61
0
bool isDirection(QStringView str) {
62
0
    return str == "normal"_L1 || str == "reverse"_L1 || str.startsWith("alternate"_L1);
63
0
}
64
65
0
bool isTimingFunction(QStringView str) {
66
0
    return str.startsWith("linear"_L1) || str.startsWith("ease"_L1)
67
0
           || str.startsWith("step"_L1) || str.startsWith("cubic-bezier"_L1);
68
0
}
69
70
0
bool isFillMode(QStringView str) {
71
0
    return str == "none"_L1 || str == "forwards"_L1 || str == "backwards"_L1 || str == "both"_L1;
72
0
}
73
74
0
bool isPlayState(QStringView str) {
75
0
    return str == "paused"_L1 || str =="running"_L1;
76
0
}
77
78
}
79
80
QSvgCssProperties::QSvgCssProperties(const QXmlStreamAttributes &attributes)
81
803k
{
82
803k
    QRegularExpression re(";| "_L1);
83
1.16M
    for (int i = 0; i < attributes.size(); ++i) {
84
360k
        const QXmlStreamAttribute &attribute = attributes.at(i);
85
360k
        QStringView name = attribute.qualifiedName();
86
360k
        if (name.isEmpty())
87
0
            continue;
88
360k
        QStringView value = attribute.value();
89
360k
        switch (name.at(0).unicode()) {
90
91
1.16k
        case 'a':
92
1.16k
            if (name == "animation"_L1)
93
0
                shortHandtoLonghandForm(value);
94
1.16k
            if (name == "animation-name"_L1)
95
0
                m_names = value.split(re, Qt::SkipEmptyParts);
96
1.16k
            if (name == "animation-duration"_L1)
97
0
                m_durations = value.split(re, Qt::SkipEmptyParts);
98
1.16k
            if (name == "animation-delay"_L1)
99
0
                m_delays = value.split(re, Qt::SkipEmptyParts);
100
1.16k
            if (name == "animation-iteration-count"_L1)
101
0
                m_iterationCounts = value.split(re, Qt::SkipEmptyParts);
102
1.16k
            if (name == "animation-direction"_L1)
103
0
                m_directions = value.split(re, Qt::SkipEmptyParts);
104
1.16k
            if (name == "animation-timing-function"_L1)
105
0
                m_timingFunctions = value.split(re, Qt::SkipEmptyParts);
106
1.16k
            if (name == "animation-fill-mode"_L1)
107
0
                m_fillModes = value.split(re, Qt::SkipEmptyParts);
108
1.16k
            if (name == "animation-play-state"_L1)
109
0
                m_playStates = value.split(re, Qt::SkipEmptyParts);
110
1.16k
            break;
111
112
7.30k
        case 'o':
113
7.30k
            if (name == "offset-path"_L1)
114
0
                m_offsetPath = value;
115
7.30k
            if (name == "offset-distance"_L1)
116
0
                m_offsetDistance = value;
117
7.30k
            if (name == "offset-rotate"_L1)
118
0
                m_offsetRotate = value;
119
7.30k
            break;
120
121
351k
        default:
122
351k
            break;
123
360k
        }
124
360k
    }
125
803k
}
126
127
QList<QSvgAnimationProperty> QSvgCssProperties::animations() const
128
803k
{
129
803k
    bool ok;
130
803k
    QList<QSvgAnimationProperty> parsedProperties;
131
803k
    for (int i = 0; i < m_names.size(); i++) {
132
0
        QSvgAnimationProperty property;
133
0
        property.name = m_names.at(i);
134
135
0
        if (!m_durations.isEmpty()) {
136
0
            QStringView durationStr = m_durations.at(i % m_durations.size());
137
0
            int duration = parseCssClockValue(durationStr, &ok);
138
0
            property.duration = ok ? duration : 0;
139
0
        }
140
141
0
        if (!m_delays.isEmpty()) {
142
0
            QStringView delayStr = m_delays.at(i % m_delays.size());
143
0
            int delay = parseCssClockValue(delayStr, &ok);
144
0
            property.delay = ok ? delay : 0;
145
0
        }
146
147
0
        if (!m_iterationCounts.isEmpty()) {
148
0
            QStringView iterationStr = m_iterationCounts.at(i % m_iterationCounts.size());
149
0
            int iteration;
150
0
            if (iterationStr == "infinite"_L1) {
151
0
                iteration = -1;
152
0
            } else {
153
0
                qreal count = iterationStr.toDouble(&ok);
154
0
                iteration = ok ? qMax(1.0, count) : 0;
155
0
            }
156
0
            property.iteration = iteration;
157
0
        }
158
159
0
        if (!m_timingFunctions.isEmpty()) {
160
0
            QStringView timingFunctionStr = m_timingFunctions.at(i % m_timingFunctions.size());
161
0
            if (timingFunctionStr == "linear"_L1) {
162
0
                property.easingFunction = EasingFunction::Linear;
163
0
            } else if (timingFunctionStr == "ease-in"_L1) {
164
0
                property.easingFunction = EasingFunction::EaseIn;
165
0
            } else if (timingFunctionStr == "ease-out"_L1) {
166
0
                property.easingFunction = EasingFunction::EaseOut;
167
0
            } else if (timingFunctionStr == "ease-in-out"_L1) {
168
0
                property.easingFunction = EasingFunction::EaseInOut;
169
0
            } else if (timingFunctionStr == "step-end"_L1) {
170
0
                property.easingFunction = EasingFunction::Steps;
171
0
                property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::End};
172
0
            } else if (timingFunctionStr == "step-start"_L1) {
173
0
                property.easingFunction = EasingFunction::Steps;
174
0
                property.easingValues = StepValues{quint32(1), QSvgCssValues::StepPosition::Start};
175
0
            }
176
0
        }
177
0
        parsedProperties.append(property);
178
0
    }
179
180
803k
    return parsedProperties;
181
803k
}
182
183
QSvgOffsetProperty QSvgCssProperties::offset() const
184
803k
{
185
803k
    QSvgOffsetProperty offset;
186
187
803k
    static const QRegularExpression rx(uR"-(^\s*path\((['"]{1})([\s\+\-,.\w]*)\1{1}\))-"_s);
188
803k
    QRegularExpressionMatch m = rx.matchView(m_offsetPath);
189
803k
    if (m.hasMatch()) {
190
0
        QStringView offsetPath = m.capturedView(2);
191
0
        offset.path = QGuiSvg::parsePath(offsetPath);
192
0
    }
193
194
803k
    qsizetype index;
195
803k
    Qt::CaseSensitivity cs = Qt::CaseInsensitive;
196
803k
    if ((index = m_offsetRotate.indexOf("auto"_L1, 0, cs)) >= 0) {
197
0
        std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 4));
198
0
        offset.rotateType = angle ? QtSvg::OffsetRotateType::AutoAngle :
199
0
                                QtSvg::OffsetRotateType::Auto;
200
0
        offset.angle = angle.value_or(0);
201
803k
    } else if ((index = m_offsetRotate.indexOf("reverse"_L1, 0, cs)) >= 0) {
202
0
        std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate.sliced(index + 7));
203
0
        offset.rotateType = angle ? QtSvg::OffsetRotateType::ReverseAngle :
204
0
                                QtSvg::OffsetRotateType::Reverse;
205
0
        offset.angle = angle.value_or(0);
206
803k
    } else {
207
803k
        std::optional<qreal> angle = QGuiSvg::parseAngle(m_offsetRotate);
208
803k
        offset.rotateType = angle ? QtSvg::OffsetRotateType::Angle :
209
803k
                                QtSvg::OffsetRotateType::Auto;
210
803k
        offset.angle = angle.value_or(0);
211
803k
    }
212
213
803k
    QGuiSvg::LengthType type;
214
803k
    bool *ok = nullptr;
215
803k
    offset.distance= QGuiSvg::parseLength(m_offsetDistance, &type, ok);
216
803k
    if (type == QGuiSvg::LengthType::LT_PERCENT) {
217
0
        offset.distance = offset.distance / 100.0;
218
0
    }
219
220
803k
    return offset;
221
803k
}
222
223
void QSvgCssProperties::shortHandtoLonghandForm(QStringView value)
224
0
{
225
0
    m_names.clear();
226
0
    m_durations.clear();
227
0
    m_delays.clear();
228
0
    m_iterationCounts.clear();
229
0
    m_directions.clear();
230
0
    m_timingFunctions.clear();
231
0
    m_fillModes.clear();
232
0
    m_playStates.clear();
233
234
0
    enum Property : uchar {
235
0
        Duration = 1,
236
0
        Delay = 1 << 1,
237
0
        IterationCount = 1 << 2,
238
0
        Direction = 1 << 3,
239
0
        TimingFunction = 1 << 4,
240
0
        FillMode = 1 << 5,
241
0
        PlayState = 1 << 6,
242
0
        Name = 1 << 7
243
0
    };
244
245
0
    const QList<QStringView> animations = value.split(QLatin1Char(';'), Qt::SkipEmptyParts);
246
0
    for (const QStringView &animation : animations) {
247
0
        uchar propertyFlag = 0;
248
0
        const QList<QStringView> animationProperties = animation.split(QLatin1Char(' '), Qt::SkipEmptyParts);
249
0
        for (const QStringView &property : animationProperties) {
250
0
            if (!(propertyFlag & Property::Duration) && isTimeValue(property)) {
251
0
                m_durations.append(property);
252
0
                propertyFlag |= Property::Duration;
253
0
            } else if (!(propertyFlag & Property::Delay) && isTimeValue(property)) {
254
0
                m_delays.append(property);
255
0
                propertyFlag |= Property::Delay;
256
0
            } else if (!(propertyFlag & Property::IterationCount) && isIteration(property)) {
257
0
                m_iterationCounts.append(property);
258
0
                propertyFlag |= Property::IterationCount;
259
0
            } else if (!(propertyFlag & Property::Direction) && isDirection(property)) {
260
0
                m_directions.append(property);
261
0
                propertyFlag |= Property::Direction;
262
0
            } else if (!(propertyFlag & Property::TimingFunction) && isTimingFunction(property)) {
263
0
                m_timingFunctions.append(property);
264
0
                propertyFlag |= Property::TimingFunction;
265
0
            } else if (!(propertyFlag & Property::FillMode) && isFillMode(property)) {
266
0
                m_fillModes.append(property);
267
0
                propertyFlag |= Property::FillMode;
268
0
            }  else if (!(propertyFlag & Property::PlayState)&& isPlayState(property)) {
269
0
                m_playStates.append(property);
270
0
                propertyFlag |= Property::PlayState;
271
0
            } else {
272
0
                m_names.append(property);
273
0
                propertyFlag |= Property::Name;
274
0
            }
275
0
        }
276
0
    }
277
0
}
278
279
QT_END_NAMESPACE