2 * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "core/html/MediaFragmentURIParser.h"
29 #include "wtf/text/CString.h"
30 #include "wtf/text/StringBuilder.h"
31 #include "wtf/text/WTFString.h"
35 const int secondsPerHour
= 3600;
36 const int secondsPerMinute
= 60;
37 const unsigned nptIdentiferLength
= 4; // "npt:"
39 static String
collectDigits(const LChar
* input
, unsigned length
, unsigned& position
)
43 // http://www.ietf.org/rfc/rfc2326.txt
44 // DIGIT ; any positive number
45 while (position
< length
&& isASCIIDigit(input
[position
]))
46 digits
.append(input
[position
++]);
47 return digits
.toString();
50 static String
collectFraction(const LChar
* input
, unsigned length
, unsigned& position
)
54 // http://www.ietf.org/rfc/rfc2326.txt
56 if (input
[position
] != '.')
59 digits
.append(input
[position
++]);
60 while (position
< length
&& isASCIIDigit(input
[position
]))
61 digits
.append(input
[position
++]);
62 return digits
.toString();
65 MediaFragmentURIParser::MediaFragmentURIParser(const KURL
& url
)
68 , m_startTime(std::numeric_limits
<double>::quiet_NaN())
69 , m_endTime(std::numeric_limits
<double>::quiet_NaN())
73 double MediaFragmentURIParser::startTime()
76 return std::numeric_limits
<double>::quiet_NaN();
77 if (m_timeFormat
== None
)
82 double MediaFragmentURIParser::endTime()
85 return std::numeric_limits
<double>::quiet_NaN();
86 if (m_timeFormat
== None
)
91 void MediaFragmentURIParser::parseFragments()
93 if (!m_url
.hasFragmentIdentifier())
95 String fragmentString
= m_url
.fragmentIdentifier();
96 if (fragmentString
.isEmpty())
100 unsigned end
= fragmentString
.length();
101 while (offset
< end
) {
102 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#processing-name-value-components
103 // 1. Parse the octet string according to the namevalues syntax, yielding a list of
104 // name-value pairs, where name and value are both octet string. In accordance
105 // with RFC 3986, the name and value components must be parsed and separated before
106 // percent-encoded octets are decoded.
107 size_t parameterStart
= offset
;
108 size_t parameterEnd
= fragmentString
.find('&', offset
);
109 if (parameterEnd
== kNotFound
)
112 size_t equalOffset
= fragmentString
.find('=', offset
);
113 if (equalOffset
== kNotFound
|| equalOffset
> parameterEnd
) {
114 offset
= parameterEnd
+ 1;
118 // 2. For each name-value pair:
119 // a. Decode percent-encoded octets in name and value as defined by RFC 3986. If either
120 // name or value are not valid percent-encoded strings, then remove the name-value pair
122 String name
= decodeURLEscapeSequences(fragmentString
.substring(parameterStart
, equalOffset
- parameterStart
));
124 if (equalOffset
!= parameterEnd
)
125 value
= decodeURLEscapeSequences(fragmentString
.substring(equalOffset
+ 1, parameterEnd
- equalOffset
- 1));
127 // b. Convert name and value to Unicode strings by interpreting them as UTF-8. If either
128 // name or value are not valid UTF-8 strings, then remove the name-value pair from the list.
129 bool validUTF8
= true;
130 if (!name
.isEmpty()) {
131 name
= name
.utf8(StrictUTF8Conversion
).data();
132 validUTF8
= !name
.isEmpty();
134 if (validUTF8
&& !value
.isEmpty()) {
135 value
= value
.utf8(StrictUTF8Conversion
).data();
136 validUTF8
= !value
.isEmpty();
140 m_fragments
.append(std::make_pair(name
, value
));
142 offset
= parameterEnd
+ 1;
146 void MediaFragmentURIParser::parseTimeFragment()
148 ASSERT(m_timeFormat
== None
);
150 if (m_fragments
.isEmpty())
153 m_timeFormat
= Invalid
;
155 for (unsigned i
= 0; i
< m_fragments
.size(); ++i
) {
156 pair
<String
, String
>& fragment
= m_fragments
[i
];
158 ASSERT(fragment
.first
.is8Bit());
159 ASSERT(fragment
.second
.is8Bit());
161 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#naming-time
162 // Temporal clipping is denoted by the name t, and specified as an interval with a begin
163 // time and an end time
164 if (fragment
.first
!= "t")
167 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#npt-time
168 // Temporal clipping can be specified either as Normal Play Time (npt) RFC 2326, as SMPTE timecodes,
169 // SMPTE, or as real-world clock time (clock) RFC 2326. Begin and end times are always specified
170 // in the same format. The format is specified by name, followed by a colon (:), with npt: being
173 double start
= std::numeric_limits
<double>::quiet_NaN();
174 double end
= std::numeric_limits
<double>::quiet_NaN();
175 if (parseNPTFragment(fragment
.second
.characters8(), fragment
.second
.length(), start
, end
)) {
178 m_timeFormat
= NormalPlayTime
;
180 // Although we have a valid fragment, don't return yet because when a fragment dimensions
181 // occurs multiple times, only the last occurrence of that dimension is used:
182 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#error-uri-general
183 // Multiple occurrences of the same dimension: only the last valid occurrence of a dimension
184 // (e.g., t=10 in #t=2&t=10) is interpreted, all previous occurrences (valid or invalid)
185 // SHOULD be ignored by the UA.
191 bool MediaFragmentURIParser::parseNPTFragment(const LChar
* timeString
, unsigned length
, double& startTime
, double& endTime
)
194 if (length
>= nptIdentiferLength
&& timeString
[0] == 'n' && timeString
[1] == 'p' && timeString
[2] == 't' && timeString
[3] == ':')
195 offset
+= nptIdentiferLength
;
197 if (offset
== length
)
200 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#naming-time
201 // If a single number only is given, this corresponds to the begin time except if it is preceded
202 // by a comma that would in this case indicate the end time.
203 if (timeString
[offset
] == ',') {
206 if (!parseNPTTime(timeString
, length
, offset
, startTime
))
210 if (offset
== length
)
213 if (timeString
[offset
] != ',')
215 if (++offset
== length
)
218 if (!parseNPTTime(timeString
, length
, offset
, endTime
))
221 if (offset
!= length
)
224 if (startTime
>= endTime
)
230 bool MediaFragmentURIParser::parseNPTTime(const LChar
* timeString
, unsigned length
, unsigned& offset
, double& time
)
232 enum Mode
{ Minutes
, Hours
};
235 if (offset
>= length
|| !isASCIIDigit(timeString
[offset
]))
238 // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#npttimedef
239 // Normal Play Time can either be specified as seconds, with an optional
240 // fractional part to indicate miliseconds, or as colon-separated hours,
241 // minutes and seconds (again with an optional fraction). Minutes and
242 // seconds must be specified as exactly two digits, hours and fractional
243 // seconds can be any number of digits. The hours, minutes and seconds
244 // specification for NPT is a convenience only, it does not signal frame
245 // accuracy. The specification of the "npt:" identifier is optional since
246 // NPT is the default time scheme. This specification builds on the RTSP
247 // specification of NPT RFC 2326.
249 // ; defined in RFC 2326
250 // npt-sec = 1*DIGIT [ "." *DIGIT ] ; definitions taken
251 // npt-hhmmss = npt-hh ":" npt-mm ":" npt-ss [ "." *DIGIT] ; from RFC 2326
252 // npt-mmss = npt-mm ":" npt-ss [ "." *DIGIT]
253 // npt-hh = 1*DIGIT ; any positive number
254 // npt-mm = 2DIGIT ; 0-59
255 // npt-ss = 2DIGIT ; 0-59
257 String digits1
= collectDigits(timeString
, length
, offset
);
258 int value1
= digits1
.toInt();
259 if (offset
>= length
|| timeString
[offset
] == ',') {
265 if (timeString
[offset
] == '.') {
266 if (offset
== length
)
268 String digits
= collectFraction(timeString
, length
, offset
);
269 fraction
= digits
.toDouble();
270 time
= value1
+ fraction
;
274 if (digits1
.length() < 2)
276 if (digits1
.length() > 2)
279 // Collect the next sequence of 0-9 after ':'
280 if (offset
>= length
|| timeString
[offset
++] != ':')
282 if (offset
>= length
|| !isASCIIDigit(timeString
[(offset
)]))
284 String digits2
= collectDigits(timeString
, length
, offset
);
285 int value2
= digits2
.toInt();
286 if (digits2
.length() != 2)
289 // Detect whether this timestamp includes hours.
291 if (mode
== Hours
|| (offset
< length
&& timeString
[offset
] == ':')) {
292 if (offset
>= length
|| timeString
[offset
++] != ':')
294 if (offset
>= length
|| !isASCIIDigit(timeString
[offset
]))
296 String digits3
= collectDigits(timeString
, length
, offset
);
297 if (digits3
.length() != 2)
299 value3
= digits3
.toInt();
306 if (offset
< length
&& timeString
[offset
] == '.')
307 fraction
= collectFraction(timeString
, length
, offset
).toDouble();
309 time
= (value1
* secondsPerHour
) + (value2
* secondsPerMinute
) + value3
+ fraction
;