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