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