Coverage Report

Created: 2026-02-26 07:48

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