MediaFragmentURIParser.cpp (9004B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "MediaFragmentURIParser.h" 8 9 #include <utility> 10 11 #include "nsCharSeparatedTokenizer.h" 12 #include "nsEscape.h" 13 #include "nsIURI.h" 14 #include "nsTArray.h" 15 16 using std::make_pair; 17 using std::pair; 18 19 namespace mozilla { 20 21 MediaFragmentURIParser::MediaFragmentURIParser(nsIURI* aURI) 22 : mClipUnit(eClipUnit_Pixel) { 23 nsAutoCString ref; 24 aURI->GetRef(ref); 25 Parse(ref); 26 } 27 28 MediaFragmentURIParser::MediaFragmentURIParser(nsCString& aRef) 29 : mClipUnit(eClipUnit_Pixel) { 30 Parse(aRef); 31 } 32 33 bool MediaFragmentURIParser::ParseNPT(nsDependentSubstring aString) { 34 nsDependentSubstring original(aString); 35 if (aString.Length() > 4 && aString[0] == 'n' && aString[1] == 'p' && 36 aString[2] == 't' && aString[3] == ':') { 37 aString.Rebind(aString, 4); 38 } 39 40 if (aString.Length() == 0) { 41 return false; 42 } 43 44 double start = -1.0; 45 double end = -1.0; 46 47 ParseNPTTime(aString, start); 48 49 if (aString.Length() == 0) { 50 mStart.emplace(start); 51 return true; 52 } 53 54 if (aString[0] != ',') { 55 aString.Rebind(original, 0); 56 return false; 57 } 58 59 aString.Rebind(aString, 1); 60 61 if (aString.Length() == 0) { 62 aString.Rebind(original, 0); 63 return false; 64 } 65 66 ParseNPTTime(aString, end); 67 68 if (end <= start || aString.Length() != 0) { 69 aString.Rebind(original, 0); 70 return false; 71 } 72 73 mStart.emplace(start); 74 mEnd.emplace(end); 75 return true; 76 } 77 78 bool MediaFragmentURIParser::ParseNPTTime(nsDependentSubstring& aString, 79 double& aTime) { 80 if (aString.Length() == 0) { 81 return false; 82 } 83 84 return ParseNPTHHMMSS(aString, aTime) || ParseNPTMMSS(aString, aTime) || 85 ParseNPTSec(aString, aTime); 86 } 87 88 // Return true if the given character is a numeric character 89 static bool IsDigit(nsDependentSubstring::char_type aChar) { 90 return (aChar >= '0' && aChar <= '9'); 91 } 92 93 // Return the index of the first character in the string that is not 94 // a numerical digit, starting from 'aStart'. 95 static uint32_t FirstNonDigit(nsDependentSubstring& aString, uint32_t aStart) { 96 while (aStart < aString.Length() && IsDigit(aString[aStart])) { 97 ++aStart; 98 } 99 return aStart; 100 } 101 102 bool MediaFragmentURIParser::ParseNPTSec(nsDependentSubstring& aString, 103 double& aSec) { 104 nsDependentSubstring original(aString); 105 if (aString.Length() == 0) { 106 return false; 107 } 108 109 uint32_t index = FirstNonDigit(aString, 0); 110 if (index == 0) { 111 return false; 112 } 113 114 nsDependentSubstring n(aString, 0, index); 115 nsresult ec; 116 int32_t s = n.ToInteger(&ec); 117 if (NS_FAILED(ec)) { 118 return false; 119 } 120 121 aString.Rebind(aString, index); 122 double fraction = 0.0; 123 if (!ParseNPTFraction(aString, fraction)) { 124 aString.Rebind(original, 0); 125 return false; 126 } 127 128 aSec = s + fraction; 129 return true; 130 } 131 132 bool MediaFragmentURIParser::ParseNPTMMSS(nsDependentSubstring& aString, 133 double& aTime) { 134 nsDependentSubstring original(aString); 135 uint32_t mm = 0; 136 uint32_t ss = 0; 137 double fraction = 0.0; 138 if (!ParseNPTMM(aString, mm)) { 139 aString.Rebind(original, 0); 140 return false; 141 } 142 143 if (aString.Length() < 2 || aString[0] != ':') { 144 aString.Rebind(original, 0); 145 return false; 146 } 147 148 aString.Rebind(aString, 1); 149 if (!ParseNPTSS(aString, ss)) { 150 aString.Rebind(original, 0); 151 return false; 152 } 153 154 if (!ParseNPTFraction(aString, fraction)) { 155 aString.Rebind(original, 0); 156 return false; 157 } 158 aTime = mm * 60 + ss + fraction; 159 return true; 160 } 161 162 bool MediaFragmentURIParser::ParseNPTFraction(nsDependentSubstring& aString, 163 double& aFraction) { 164 double fraction = 0.0; 165 166 if (aString.Length() > 0 && aString[0] == '.') { 167 uint32_t index = FirstNonDigit(aString, 1); 168 169 if (index > 1) { 170 nsDependentSubstring number(aString, 0, index); 171 nsresult ec; 172 fraction = PromiseFlatString(number).ToDouble(&ec); 173 if (NS_FAILED(ec)) { 174 return false; 175 } 176 } 177 aString.Rebind(aString, index); 178 } 179 180 aFraction = fraction; 181 return true; 182 } 183 184 bool MediaFragmentURIParser::ParseNPTHHMMSS(nsDependentSubstring& aString, 185 double& aTime) { 186 nsDependentSubstring original(aString); 187 uint32_t hh = 0; 188 double seconds = 0.0; 189 if (!ParseNPTHH(aString, hh)) { 190 return false; 191 } 192 193 if (aString.Length() < 2 || aString[0] != ':') { 194 aString.Rebind(original, 0); 195 return false; 196 } 197 198 aString.Rebind(aString, 1); 199 if (!ParseNPTMMSS(aString, seconds)) { 200 aString.Rebind(original, 0); 201 return false; 202 } 203 204 aTime = hh * 3600 + seconds; 205 return true; 206 } 207 208 bool MediaFragmentURIParser::ParseNPTHH(nsDependentSubstring& aString, 209 uint32_t& aHour) { 210 if (aString.Length() == 0) { 211 return false; 212 } 213 214 uint32_t index = FirstNonDigit(aString, 0); 215 if (index == 0) { 216 return false; 217 } 218 219 nsDependentSubstring n(aString, 0, index); 220 nsresult ec; 221 int32_t u = n.ToInteger(&ec); 222 if (NS_FAILED(ec)) { 223 return false; 224 } 225 226 aString.Rebind(aString, index); 227 aHour = u; 228 return true; 229 } 230 231 bool MediaFragmentURIParser::ParseNPTMM(nsDependentSubstring& aString, 232 uint32_t& aMinute) { 233 return ParseNPTSS(aString, aMinute); 234 } 235 236 bool MediaFragmentURIParser::ParseNPTSS(nsDependentSubstring& aString, 237 uint32_t& aSecond) { 238 if (aString.Length() < 2) { 239 return false; 240 } 241 242 if (IsDigit(aString[0]) && IsDigit(aString[1])) { 243 nsDependentSubstring n(aString, 0, 2); 244 nsresult ec; 245 int32_t u = n.ToInteger(&ec); 246 if (NS_FAILED(ec)) { 247 return false; 248 } 249 250 aString.Rebind(aString, 2); 251 if (u >= 60) return false; 252 253 aSecond = u; 254 return true; 255 } 256 257 return false; 258 } 259 260 static bool ParseInteger(nsDependentSubstring& aString, int32_t& aResult) { 261 uint32_t index = FirstNonDigit(aString, 0); 262 if (index == 0) { 263 return false; 264 } 265 266 nsDependentSubstring n(aString, 0, index); 267 nsresult ec; 268 int32_t s = n.ToInteger(&ec); 269 if (NS_FAILED(ec)) { 270 return false; 271 } 272 273 aString.Rebind(aString, index); 274 aResult = s; 275 return true; 276 } 277 278 static bool ParseCommaSeparator(nsDependentSubstring& aString) { 279 if (aString.Length() > 1 && aString[0] == ',') { 280 aString.Rebind(aString, 1); 281 return true; 282 } 283 284 return false; 285 } 286 287 bool MediaFragmentURIParser::ParseXYWH(nsDependentSubstring aString) { 288 int32_t x, y, w, h; 289 ClipUnit clipUnit; 290 291 // Determine units. 292 if (StringBeginsWith(aString, u"pixel:"_ns)) { 293 clipUnit = eClipUnit_Pixel; 294 aString.Rebind(aString, 6); 295 } else if (StringBeginsWith(aString, u"percent:"_ns)) { 296 clipUnit = eClipUnit_Percent; 297 aString.Rebind(aString, 8); 298 } else { 299 clipUnit = eClipUnit_Pixel; 300 } 301 302 // Read and validate coordinates. 303 if (ParseInteger(aString, x) && x >= 0 && ParseCommaSeparator(aString) && 304 ParseInteger(aString, y) && y >= 0 && ParseCommaSeparator(aString) && 305 ParseInteger(aString, w) && w > 0 && ParseCommaSeparator(aString) && 306 ParseInteger(aString, h) && h > 0 && aString.Length() == 0) { 307 // Reject invalid percentage coordinates. 308 if (clipUnit == eClipUnit_Percent && (x + w > 100 || y + h > 100)) { 309 return false; 310 } 311 312 mClip.emplace(x, y, w, h); 313 mClipUnit = clipUnit; 314 return true; 315 } 316 317 return false; 318 } 319 320 void MediaFragmentURIParser::Parse(nsACString& aRef) { 321 // Create an array of possibly-invalid media fragments. 322 nsTArray<std::pair<nsCString, nsCString> > fragments; 323 324 for (const nsACString& nv : nsCCharSeparatedTokenizer(aRef, '&').ToRange()) { 325 int32_t index = nv.FindChar('='); 326 if (index >= 0) { 327 nsAutoCString name; 328 nsAutoCString value; 329 NS_UnescapeURL(StringHead(nv, index), esc_Ref | esc_AlwaysCopy, name); 330 NS_UnescapeURL(Substring(nv, index + 1, nv.Length()), 331 esc_Ref | esc_AlwaysCopy, value); 332 fragments.AppendElement(make_pair(name, value)); 333 } 334 } 335 336 // Parse the media fragment values. 337 bool gotTemporal = false, gotSpatial = false; 338 for (int i = fragments.Length() - 1; i >= 0; --i) { 339 if (gotTemporal && gotSpatial) { 340 // We've got one of each possible type. No need to look at the rest. 341 break; 342 } 343 if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) { 344 nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second); 345 gotTemporal = ParseNPT(nsDependentSubstring(value, 0)); 346 } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) { 347 nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second); 348 gotSpatial = ParseXYWH(nsDependentSubstring(value, 0)); 349 } 350 } 351 } 352 353 } // namespace mozilla