[Resource Timing] Gather timing information with reliable responseEnd time
[WebKit-https.git] / Source / WebCore / html / MediaFragmentURIParser.cpp
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. 
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO)
29
30 #include "MediaFragmentURIParser.h"
31
32 #include "HTMLElement.h"
33 #include "MediaPlayer.h"
34 #include "ProcessingInstruction.h"
35 #include "SegmentedString.h"
36 #include "Text.h"
37 #include <wtf/text/CString.h>
38 #include <wtf/text/StringBuilder.h>
39 #include <wtf/text/WTFString.h>
40
41 namespace WebCore {
42
43 const int secondsPerHour = 3600;
44 const int secondsPerMinute = 60;
45 const unsigned nptIdentifierLength = 4; // "npt:"
46
47 static String collectDigits(const LChar* input, unsigned length, unsigned& position)
48 {
49     StringBuilder digits;
50
51     // http://www.ietf.org/rfc/rfc2326.txt
52     // DIGIT ; any positive number
53     while (position < length && isASCIIDigit(input[position]))
54         digits.append(input[position++]);
55     return digits.toString();
56 }
57
58 static String collectFraction(const LChar* input, unsigned length, unsigned& position)
59 {
60     StringBuilder digits;
61
62     // http://www.ietf.org/rfc/rfc2326.txt
63     // [ "." *DIGIT ]
64     if (input[position] != '.')
65         return String();
66
67     digits.append(input[position++]);
68     while (position < length && isASCIIDigit(input[position]))
69         digits.append(input[position++]);
70     return digits.toString();
71 }
72
73 MediaFragmentURIParser::MediaFragmentURIParser(const URL& url)
74     : m_url(url)
75     , m_timeFormat(None)
76     , m_startTime(MediaTime::invalidTime())
77     , m_endTime(MediaTime::invalidTime())
78 {
79 }
80
81 MediaTime MediaFragmentURIParser::startTime()
82 {
83     if (!m_url.isValid())
84         return MediaTime::invalidTime();
85     if (m_timeFormat == None)
86         parseTimeFragment();
87     return m_startTime;
88 }
89
90 MediaTime MediaFragmentURIParser::endTime()
91 {
92     if (!m_url.isValid())
93         return MediaTime::invalidTime();
94     if (m_timeFormat == None)
95         parseTimeFragment();
96     return m_endTime;
97 }
98
99 void MediaFragmentURIParser::parseFragments()
100 {
101     if (!m_url.hasFragmentIdentifier())
102         return;
103     String fragmentString = m_url.fragmentIdentifier();
104     if (fragmentString.isEmpty())
105         return;
106
107     unsigned offset = 0;
108     unsigned end = fragmentString.length();
109     while (offset < end) {
110         // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#processing-name-value-components
111         // 1. Parse the octet string according to the namevalues syntax, yielding a list of 
112         //    name-value pairs, where name and value are both octet string. In accordance 
113         //    with RFC 3986, the name and value components must be parsed and separated before
114         //    percent-encoded octets are decoded.
115         size_t parameterStart = offset;
116         size_t parameterEnd = fragmentString.find('&', offset);
117         if (parameterEnd == notFound)
118             parameterEnd = end;
119
120         size_t equalOffset = fragmentString.find('=', offset);
121         if (equalOffset == notFound || equalOffset > parameterEnd) {
122             offset = parameterEnd + 1;
123             continue;
124         }
125
126         // 2. For each name-value pair:
127         //  a. Decode percent-encoded octets in name and value as defined by RFC 3986. If either
128         //     name or value are not valid percent-encoded strings, then remove the name-value pair
129         //     from the list.
130         String name = decodeURLEscapeSequences(fragmentString.substring(parameterStart, equalOffset - parameterStart));
131         String value;
132         if (equalOffset != parameterEnd)
133             value = decodeURLEscapeSequences(fragmentString.substring(equalOffset + 1, parameterEnd - equalOffset - 1));
134         
135         //  b. Convert name and value to Unicode strings by interpreting them as UTF-8. If either
136         //     name or value are not valid UTF-8 strings, then remove the name-value pair from the list.
137         bool validUTF8 = false;
138         if (!name.isEmpty() && !value.isEmpty()) {
139             name = name.utf8(StrictConversion).data();
140             validUTF8 = !name.isEmpty();
141
142             if (validUTF8) {
143                 value = value.utf8(StrictConversion).data();
144                 validUTF8 = !value.isEmpty();
145             }
146         }
147         
148         if (validUTF8)
149             m_fragments.append(std::make_pair(name, value));
150
151         offset = parameterEnd + 1;
152     }
153 }
154
155 void MediaFragmentURIParser::parseTimeFragment()
156 {
157     ASSERT(m_timeFormat == None);
158
159     if (m_fragments.isEmpty())
160         parseFragments();
161
162     m_timeFormat = Invalid;
163
164     for (auto& fragment : m_fragments) {
165         ASSERT(fragment.first.is8Bit());
166         ASSERT(fragment.second.is8Bit());
167
168         // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#naming-time
169         // Temporal clipping is denoted by the name t, and specified as an interval with a begin 
170         // time and an end time
171         if (fragment.first != "t")
172             continue;
173
174         // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#npt-time
175         // Temporal clipping can be specified either as Normal Play Time (npt) RFC 2326, as SMPTE timecodes,
176         // SMPTE, or as real-world clock time (clock) RFC 2326. Begin and end times are always specified
177         // in the same format. The format is specified by name, followed by a colon (:), with npt: being
178         // the default.
179         
180         MediaTime start = MediaTime::invalidTime();
181         MediaTime end = MediaTime::invalidTime();
182         if (parseNPTFragment(fragment.second.characters8(), fragment.second.length(), start, end)) {
183             m_startTime = start;
184             m_endTime = end;
185             m_timeFormat = NormalPlayTime;
186
187             // Although we have a valid fragment, don't return yet because when a fragment dimensions
188             // occurs multiple times, only the last occurrence of that dimension is used:
189             // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#error-uri-general
190             // Multiple occurrences of the same dimension: only the last valid occurrence of a dimension
191             // (e.g., t=10 in #t=2&t=10) is interpreted, all previous occurrences (valid or invalid) 
192             // SHOULD be ignored by the UA. 
193         }
194     }
195     m_fragments.clear();
196 }
197
198 bool MediaFragmentURIParser::parseNPTFragment(const LChar* timeString, unsigned length, MediaTime& startTime, MediaTime& endTime)
199 {
200     unsigned offset = 0;
201     if (length >= nptIdentifierLength && timeString[0] == 'n' && timeString[1] == 'p' && timeString[2] == 't' && timeString[3] == ':')
202         offset += nptIdentifierLength;
203
204     if (offset == length)
205         return false;
206     
207     // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#naming-time
208     // If a single number only is given, this corresponds to the begin time except if it is preceded
209     // by a comma that would in this case indicate the end time.
210     if (timeString[offset] == ',')
211         startTime = MediaTime::zeroTime();
212     else {
213         if (!parseNPTTime(timeString, length, offset, startTime))
214             return false;
215     }
216
217     if (offset == length)
218         return true;
219
220     if (timeString[offset] != ',')
221         return false;
222     if (++offset == length)
223         return false;
224
225     if (!parseNPTTime(timeString, length, offset, endTime))
226         return false;
227
228     if (offset != length)
229         return false;
230     
231     if (startTime >= endTime)
232         return false;
233
234     return true;
235 }
236
237 bool MediaFragmentURIParser::parseNPTTime(const LChar* timeString, unsigned length, unsigned& offset, MediaTime& time)
238 {
239     enum Mode { minutes, hours };
240     Mode mode = minutes;
241
242     if (offset >= length || !isASCIIDigit(timeString[offset]))
243         return false;
244
245     // http://www.w3.org/2008/WebVideo/Fragments/WD-media-fragments-spec/#npttimedef
246     // Normal Play Time can either be specified as seconds, with an optional
247     // fractional part to indicate milliseconds, or as colon-separated hours,
248     // minutes and seconds (again with an optional fraction). Minutes and
249     // seconds must be specified as exactly two digits, hours and fractional
250     // seconds can be any number of digits. The hours, minutes and seconds
251     // specification for NPT is a convenience only, it does not signal frame
252     // accuracy. The specification of the "npt:" identifier is optional since
253     // NPT is the default time scheme. This specification builds on the RTSP
254     // specification of NPT RFC 2326.
255     //
256     // ; defined in RFC 2326
257     // npt-sec       = 1*DIGIT [ "." *DIGIT ]                     ; definitions taken
258     // npt-hhmmss    = npt-hh ":" npt-mm ":" npt-ss [ "." *DIGIT] ; from RFC 2326
259     // npt-mmss      = npt-mm ":" npt-ss [ "." *DIGIT] 
260     // npt-hh        =   1*DIGIT     ; any positive number
261     // npt-mm        =   2DIGIT      ; 0-59
262     // npt-ss        =   2DIGIT      ; 0-59
263
264     String digits1 = collectDigits(timeString, length, offset);
265     int value1 = digits1.toInt();
266     if (offset >= length || timeString[offset] == ',') {
267         time = MediaTime::createWithDouble(value1);
268         return true;
269     }
270
271     MediaTime fraction;
272     if (timeString[offset] == '.') {
273         if (offset == length)
274             return true;
275         String digits = collectFraction(timeString, length, offset);
276         fraction = MediaTime::createWithDouble(digits.toDouble());
277         time = MediaTime::createWithDouble(value1) + fraction;
278         return true;
279     }
280     
281     if (digits1.length() < 2)
282         return false;
283     if (digits1.length() > 2)
284         mode = hours;
285
286     // Collect the next sequence of 0-9 after ':'
287     if (offset >= length || timeString[offset++] != ':')
288         return false;
289     if (offset >= length || !isASCIIDigit(timeString[(offset)]))
290         return false;
291     String digits2 = collectDigits(timeString, length, offset);
292     int value2 = digits2.toInt();
293     if (digits2.length() != 2)
294         return false;
295
296     // Detect whether this timestamp includes hours.
297     int value3;
298     if (mode == hours || (offset < length && timeString[offset] == ':')) {
299         if (offset >= length || timeString[offset++] != ':')
300             return false;
301         if (offset >= length || !isASCIIDigit(timeString[offset]))
302             return false;
303         String digits3 = collectDigits(timeString, length, offset);
304         if (digits3.length() != 2)
305             return false;
306         value3 = digits3.toInt();
307     } else {
308         value3 = value2;
309         value2 = value1;
310         value1 = 0;
311     }
312
313     if (offset < length && timeString[offset] == '.')
314         fraction = MediaTime::createWithDouble(collectFraction(timeString, length, offset).toDouble());
315     
316     time = MediaTime::createWithDouble((value1 * secondsPerHour) + (value2 * secondsPerMinute) + value3) + fraction;
317     return true;
318 }
319
320 }
321 #endif