1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/mobilemessage/src/gonk/WspPduHelper.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2899 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/wap_consts.js", this); 1.13 + 1.14 +let DEBUG; // set to true to see debug messages 1.15 + 1.16 +// Special ASCII characters 1.17 +const NUL = 0; 1.18 +const CR = 13; 1.19 +const LF = 10; 1.20 +const SP = 32; 1.21 +const HT = 9; 1.22 +const DQUOTE = 34; 1.23 +const DEL = 127; 1.24 + 1.25 +// Special ASCII character ranges 1.26 +const CTLS = 32; 1.27 +const ASCIIS = 128; 1.28 + 1.29 +/** 1.30 + * Error class for generic encoding/decoding failures. 1.31 + */ 1.32 +this.CodeError = function CodeError(message) { 1.33 + this.name = "CodeError"; 1.34 + this.message = message || "Invalid format"; 1.35 +} 1.36 +CodeError.prototype = new Error(); 1.37 +CodeError.prototype.constructor = CodeError; 1.38 + 1.39 +/** 1.40 + * Error class for unexpected NUL char at decoding text elements. 1.41 + * 1.42 + * @param message [optional] 1.43 + * A short description for the error. 1.44 + */ 1.45 +function NullCharError(message) { 1.46 + this.name = "NullCharError"; 1.47 + this.message = message || "Null character found"; 1.48 +} 1.49 +NullCharError.prototype = new CodeError(); 1.50 +NullCharError.prototype.constructor = NullCharError; 1.51 + 1.52 +/** 1.53 + * Error class for fatal encoding/decoding failures. 1.54 + * 1.55 + * This error is only raised when expected format isn't met and the parser 1.56 + * context can't do anything more to either skip it or hand over to other 1.57 + * alternative encoding/decoding steps. 1.58 + * 1.59 + * @param message [optional] 1.60 + * A short description for the error. 1.61 + */ 1.62 +this.FatalCodeError = function FatalCodeError(message) { 1.63 + this.name = "FatalCodeError"; 1.64 + this.message = message || "Decoding fails"; 1.65 +} 1.66 +FatalCodeError.prototype = new Error(); 1.67 +FatalCodeError.prototype.constructor = FatalCodeError; 1.68 + 1.69 +/** 1.70 + * Error class for undefined well known encoding. 1.71 + * 1.72 + * When a encoded header field/parameter has unknown/unsupported value, we may 1.73 + * never know how to decode the next value. For example, a parameter of 1.74 + * undefined well known encoding may be followed by a Q-value, which is 1.75 + * basically a uintvar. However, there is no way you can distiguish an Q-value 1.76 + * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41. 1.77 + * The `skipValue` will try the latter one, which is not expected. 1.78 + * 1.79 + * @param message [optional] 1.80 + * A short description for the error. 1.81 + */ 1.82 +this.NotWellKnownEncodingError = function NotWellKnownEncodingError(message) { 1.83 + this.name = "NotWellKnownEncodingError"; 1.84 + this.message = message || "Not well known encoding"; 1.85 +} 1.86 +NotWellKnownEncodingError.prototype = new FatalCodeError(); 1.87 +NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError; 1.88 + 1.89 +/** 1.90 + * Internal helper function to retrieve the value of a property with its name 1.91 + * specified by `name` inside the object `headers`. 1.92 + * 1.93 + * @param headers 1.94 + * An object that contains parsed header fields. 1.95 + * @param name 1.96 + * Header name string to be checked. 1.97 + * 1.98 + * @return Value of specified header field. 1.99 + * 1.100 + * @throws FatalCodeError if headers[name] is undefined. 1.101 + */ 1.102 +this.ensureHeader = function ensureHeader(headers, name) { 1.103 + let value = headers[name]; 1.104 + // Header field might have a null value as NoValue 1.105 + if (value === undefined) { 1.106 + throw new FatalCodeError("ensureHeader: header " + name + " not defined"); 1.107 + } 1.108 + return value; 1.109 +} 1.110 + 1.111 +/** 1.112 + * Skip field value. 1.113 + * 1.114 + * The WSP field values are encoded so that the length of the field value can 1.115 + * always be determined, even if the detailed format of a specific field value 1.116 + * is not known. This makes it possible to skip over individual header fields 1.117 + * without interpreting their content. ... the first octet in all the field 1.118 + * values can be interpreted as follows: 1.119 + * 1.120 + * 0 - 30 | This octet is followed by the indicated number (0 - 30) of data 1.121 + * octets. 1.122 + * 31 | This octet is followed by a unitvar, which indicates the number 1.123 + * of data octets after it. 1.124 + * 32 - 127 | The value is a text string, terminated by a zero octet (NUL 1.125 + * character). 1.126 + * 128 - 255 | It is an encoded 7-bit value; this header has no more data. 1.127 + * 1.128 + * @param data 1.129 + * A wrapped object containing raw PDU data. 1.130 + * 1.131 + * @return Skipped value of several possible types like string, integer, or 1.132 + * an array of octets. 1.133 + * 1.134 + * @see WAP-230-WSP-20010705-a clause 8.4.1.2 1.135 + */ 1.136 +this.skipValue = function skipValue(data) { 1.137 + let begin = data.offset; 1.138 + let value = Octet.decode(data); 1.139 + if (value <= 31) { 1.140 + if (value == 31) { 1.141 + value = UintVar.decode(data); 1.142 + } 1.143 + 1.144 + if (value) { 1.145 + // `value` can be larger than 30, max length of a multi-octet integer 1.146 + // here. So we must decode it as an array instead. 1.147 + value = Octet.decodeMultiple(data, data.offset + value); 1.148 + } else { 1.149 + value = null; 1.150 + } 1.151 + } else if (value <= 127) { 1.152 + data.offset = begin; 1.153 + value = NullTerminatedTexts.decode(data); 1.154 + } else { 1.155 + value &= 0x7F; 1.156 + } 1.157 + 1.158 + return value; 1.159 +} 1.160 + 1.161 +/** 1.162 + * Helper function for decoding multiple alternative forms. 1.163 + * 1.164 + * @param data 1.165 + * A wrapped object containing raw PDU data. 1.166 + * @param options 1.167 + * Extra context for decoding. 1.168 + * 1.169 + * @return Decoded value. 1.170 + */ 1.171 +this.decodeAlternatives = function decodeAlternatives(data, options) { 1.172 + let begin = data.offset; 1.173 + for (let i = 2; i < arguments.length; i++) { 1.174 + try { 1.175 + return arguments[i].decode(data, options); 1.176 + } catch (e) { 1.177 + // Throw the last exception we get 1.178 + if (i == (arguments.length - 1)) { 1.179 + throw e; 1.180 + } 1.181 + 1.182 + data.offset = begin; 1.183 + } 1.184 + } 1.185 +} 1.186 + 1.187 +/** 1.188 + * Helper function for encoding multiple alternative forms. 1.189 + * 1.190 + * @param data 1.191 + * A wrapped object to store encoded raw data. 1.192 + * @param value 1.193 + * Object value of arbitrary type to be encoded. 1.194 + * @param options 1.195 + * Extra context for encoding. 1.196 + */ 1.197 +this.encodeAlternatives = function encodeAlternatives(data, value, options) { 1.198 + let begin = data.offset; 1.199 + for (let i = 3; i < arguments.length; i++) { 1.200 + try { 1.201 + arguments[i].encode(data, value, options); 1.202 + return; 1.203 + } catch (e) { 1.204 + // Throw the last exception we get 1.205 + if (i == (arguments.length - 1)) { 1.206 + throw e; 1.207 + } 1.208 + 1.209 + data.offset = begin; 1.210 + } 1.211 + } 1.212 +} 1.213 + 1.214 +this.Octet = { 1.215 + /** 1.216 + * @param data 1.217 + * A wrapped object containing raw PDU data. 1.218 + * 1.219 + * @throws RangeError if no more data is available. 1.220 + */ 1.221 + decode: function(data) { 1.222 + if (data.offset >= data.array.length) { 1.223 + throw new RangeError(); 1.224 + } 1.225 + 1.226 + return data.array[data.offset++]; 1.227 + }, 1.228 + 1.229 + /** 1.230 + * @param data 1.231 + * A wrapped object containing raw PDU data. 1.232 + * @param end 1.233 + * An ending offset indicating the end of octet array to read. 1.234 + * 1.235 + * @return A decoded array object. 1.236 + * 1.237 + * @throws RangeError if no enough data to read. 1.238 + * @throws TypeError if `data` has neither subarray() nor slice() method. 1.239 + */ 1.240 + decodeMultiple: function(data, end) { 1.241 + if ((end < data.offset) || (end > data.array.length)) { 1.242 + throw new RangeError(); 1.243 + } 1.244 + if (end == data.offset) { 1.245 + return []; 1.246 + } 1.247 + 1.248 + let result; 1.249 + if (data.array.subarray) { 1.250 + result = data.array.subarray(data.offset, end); 1.251 + } else if (data.array.slice) { 1.252 + result = data.array.slice(data.offset, end); 1.253 + } else { 1.254 + throw new TypeError(); 1.255 + } 1.256 + 1.257 + data.offset = end; 1.258 + return result; 1.259 + }, 1.260 + 1.261 + /** 1.262 + * Internal octet decoding for specific value. 1.263 + * 1.264 + * @param data 1.265 + * A wrapped object containing raw PDU data. 1.266 + * @param expected 1.267 + * Expected octet value. 1.268 + * 1.269 + * @return Expected octet value. 1.270 + * 1.271 + * @throws CodeError if read octet is not equal to expected one. 1.272 + */ 1.273 + decodeEqualTo: function(data, expected) { 1.274 + if (this.decode(data) != expected) { 1.275 + throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected); 1.276 + } 1.277 + 1.278 + return expected; 1.279 + }, 1.280 + 1.281 + /** 1.282 + * @param data 1.283 + * A wrapped object to store encoded raw data. 1.284 + * @param octet 1.285 + * Octet value to be encoded. 1.286 + */ 1.287 + encode: function(data, octet) { 1.288 + if (data.offset >= data.array.length) { 1.289 + data.array.push(octet); 1.290 + data.offset++; 1.291 + } else { 1.292 + data.array[data.offset++] = octet; 1.293 + } 1.294 + }, 1.295 + 1.296 + /** 1.297 + * @param data 1.298 + * A wrapped object to store encoded raw data. 1.299 + * @param octet 1.300 + * An octet array object. 1.301 + */ 1.302 + encodeMultiple: function(data, array) { 1.303 + for (let i = 0; i < array.length; i++) { 1.304 + this.encode(data, array[i]); 1.305 + } 1.306 + }, 1.307 +}; 1.308 + 1.309 +/** 1.310 + * TEXT = <any OCTET except CTLs, but including LWS> 1.311 + * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> 1.312 + * LWS = [CRLF] 1*(SP|HT) 1.313 + * CRLF = CR LF 1.314 + * CR = <US-ASCII CR, carriage return (13)> 1.315 + * LF = <US-ASCII LF, linefeed (10)> 1.316 + * SP = <US-ASCII SP, space (32)> 1.317 + * HT = <US-ASCII HT, horizontal-tab(9)> 1.318 + * 1.319 + * @see RFC 2616 clause 2.2 Basic Rules 1.320 + */ 1.321 +this.Text = { 1.322 + /** 1.323 + * @param data 1.324 + * A wrapped object containing raw PDU data. 1.325 + * 1.326 + * @return Decoded character. 1.327 + * 1.328 + * @throws NullCharError if a NUL character read. 1.329 + * @throws CodeError if a control character read. 1.330 + */ 1.331 + decode: function(data) { 1.332 + let code = Octet.decode(data); 1.333 + if ((code >= CTLS) && (code != DEL)) { 1.334 + return String.fromCharCode(code); 1.335 + } 1.336 + 1.337 + if (code == NUL) { 1.338 + throw new NullCharError(); 1.339 + } 1.340 + 1.341 + if (code != CR) { 1.342 + throw new CodeError("Text: invalid char code " + code); 1.343 + } 1.344 + 1.345 + // "A CRLF is allowed in the definition of TEXT only as part of a header 1.346 + // field continuation. It is expected that the folding LWS will be 1.347 + // replaced with a single SP before interpretation of the TEXT value." 1.348 + // ~ RFC 2616 clause 2.2 1.349 + 1.350 + let extra; 1.351 + 1.352 + // Rethrow everything as CodeError. We had already a successful read above. 1.353 + try { 1.354 + extra = Octet.decode(data); 1.355 + if (extra != LF) { 1.356 + throw new CodeError("Text: doesn't match LWS sequence"); 1.357 + } 1.358 + 1.359 + extra = Octet.decode(data); 1.360 + if ((extra != SP) && (extra != HT)) { 1.361 + throw new CodeError("Text: doesn't match LWS sequence"); 1.362 + } 1.363 + } catch (e if e instanceof CodeError) { 1.364 + throw e; 1.365 + } catch (e) { 1.366 + throw new CodeError("Text: doesn't match LWS sequence"); 1.367 + } 1.368 + 1.369 + // Let's eat as many SP|HT as possible. 1.370 + let begin; 1.371 + 1.372 + // Do not throw anything here. We had already matched (SP | HT). 1.373 + try { 1.374 + do { 1.375 + begin = data.offset; 1.376 + extra = Octet.decode(data); 1.377 + } while ((extra == SP) || (extra == HT)); 1.378 + } catch (e) {} 1.379 + 1.380 + data.offset = begin; 1.381 + return " "; 1.382 + }, 1.383 + 1.384 + /** 1.385 + * @param data 1.386 + * A wrapped object to store encoded raw data. 1.387 + * @param text 1.388 + * String text of one character to be encoded. 1.389 + * @param asciiOnly 1.390 + * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). 1.391 + * 1.392 + * @throws CodeError if a control character got. 1.393 + */ 1.394 + encode: function(data, text, asciiOnly) { 1.395 + if (!text) { 1.396 + throw new CodeError("Text: empty string"); 1.397 + } 1.398 + 1.399 + let code = text.charCodeAt(0); 1.400 + if ((code < CTLS) || (code == DEL) || (code > 255) || 1.401 + (code >= 128 && asciiOnly)) { 1.402 + throw new CodeError("Text: invalid char code " + code); 1.403 + } 1.404 + Octet.encode(data, code); 1.405 + }, 1.406 +}; 1.407 + 1.408 +this.NullTerminatedTexts = { 1.409 + /** 1.410 + * Decode internal referenced null terminated text string. 1.411 + * 1.412 + * @param data 1.413 + * A wrapped object containing raw PDU data. 1.414 + * 1.415 + * @return Decoded string. 1.416 + */ 1.417 + decode: function(data) { 1.418 + let str = ""; 1.419 + try { 1.420 + // A End-of-string is also a CTL, which should cause a error. 1.421 + while (true) { 1.422 + str += Text.decode(data); 1.423 + } 1.424 + } catch (e if e instanceof NullCharError) { 1.425 + return str; 1.426 + } 1.427 + }, 1.428 + 1.429 + /** 1.430 + * @param data 1.431 + * A wrapped object to store encoded raw data. 1.432 + * @param str 1.433 + * A String to be encoded. 1.434 + * @param asciiOnly 1.435 + * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). 1.436 + */ 1.437 + encode: function(data, str, asciiOnly) { 1.438 + if (str) { 1.439 + for (let i = 0; i < str.length; i++) { 1.440 + Text.encode(data, str.charAt(i), asciiOnly); 1.441 + } 1.442 + } 1.443 + Octet.encode(data, 0); 1.444 + }, 1.445 +}; 1.446 + 1.447 +/** 1.448 + * TOKEN = 1*<any CHAR except CTLs or separators> 1.449 + * CHAR = <any US-ASCII character (octets 0 - 127)> 1.450 + * SEPARATORS = ()<>@,;:\"/[]?={} SP HT 1.451 + * 1.452 + * @see RFC 2616 clause 2.2 Basic Rules 1.453 + */ 1.454 +this.Token = { 1.455 + /** 1.456 + * @param data 1.457 + * A wrapped object containing raw PDU data. 1.458 + * 1.459 + * @return Decoded character. 1.460 + * 1.461 + * @throws NullCharError if a NUL character read. 1.462 + * @throws CodeError if an invalid character read. 1.463 + */ 1.464 + decode: function(data) { 1.465 + let code = Octet.decode(data); 1.466 + if ((code < ASCIIS) && (code >= CTLS)) { 1.467 + if ((code == HT) || (code == SP) 1.468 + || (code == 34) || (code == 40) || (code == 41) // ASCII "() 1.469 + || (code == 44) || (code == 47) // ASCII ,/ 1.470 + || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ 1.471 + || ((code >= 91) && (code <= 93)) // ASCII [\] 1.472 + || (code == 123) || (code == 125)) { // ASCII {} 1.473 + throw new CodeError("Token: invalid char code " + code); 1.474 + } 1.475 + 1.476 + return String.fromCharCode(code); 1.477 + } 1.478 + 1.479 + if (code == NUL) { 1.480 + throw new NullCharError(); 1.481 + } 1.482 + 1.483 + throw new CodeError("Token: invalid char code " + code); 1.484 + }, 1.485 + 1.486 + /** 1.487 + * @param data 1.488 + * A wrapped object to store encoded raw data. 1.489 + * @param token 1.490 + * String text of one character to be encoded. 1.491 + * 1.492 + * @throws CodeError if an invalid character got. 1.493 + */ 1.494 + encode: function(data, token) { 1.495 + if (!token) { 1.496 + throw new CodeError("Token: empty string"); 1.497 + } 1.498 + 1.499 + let code = token.charCodeAt(0); 1.500 + if ((code < ASCIIS) && (code >= CTLS)) { 1.501 + if ((code == HT) || (code == SP) 1.502 + || (code == 34) || (code == 40) || (code == 41) // ASCII "() 1.503 + || (code == 44) || (code == 47) // ASCII ,/ 1.504 + || ((code >= 58) && (code <= 64)) // ASCII :;<=>?@ 1.505 + || ((code >= 91) && (code <= 93)) // ASCII [\] 1.506 + || (code == 123) || (code == 125)) { // ASCII {} 1.507 + // Fallback to throw CodeError 1.508 + } else { 1.509 + Octet.encode(data, token.charCodeAt(0)); 1.510 + return; 1.511 + } 1.512 + } 1.513 + 1.514 + throw new CodeError("Token: invalid char code " + code); 1.515 + }, 1.516 +}; 1.517 + 1.518 +/** 1.519 + * uric = reserved | unreserved | escaped 1.520 + * reserved = ;/?:@&=+$, 1.521 + * unreserved = alphanum | mark 1.522 + * mark = -_.!~*'() 1.523 + * escaped = % hex hex 1.524 + * excluded but used = #% 1.525 + * 1.526 + * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126 1.527 + * 1.528 + * @see RFC 2396 Uniform Resource Indentifiers (URI) 1.529 + */ 1.530 +this.URIC = { 1.531 + /** 1.532 + * @param data 1.533 + * A wrapped object containing raw PDU data. 1.534 + * 1.535 + * @return Decoded character. 1.536 + * 1.537 + * @throws NullCharError if a NUL character read. 1.538 + * @throws CodeError if an invalid character read. 1.539 + */ 1.540 + decode: function(data) { 1.541 + let code = Octet.decode(data); 1.542 + if (code == NUL) { 1.543 + throw new NullCharError(); 1.544 + } 1.545 + 1.546 + if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60) 1.547 + || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96) 1.548 + || ((code >= 123) && (code <= 125)) || (code == 127)) { 1.549 + throw new CodeError("URIC: invalid char code " + code); 1.550 + } 1.551 + 1.552 + return String.fromCharCode(code); 1.553 + }, 1.554 +}; 1.555 + 1.556 +/** 1.557 + * If the first character in the TEXT is in the range of 128-255, a Quote 1.558 + * character must precede it. Otherwise the Quote character must be omitted. 1.559 + * The Quote is not part of the contents. 1.560 + * 1.561 + * Text-string = [Quote] *TEXT End-of-string 1.562 + * Quote = <Octet 127> 1.563 + * 1.564 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.565 + */ 1.566 +this.TextString = { 1.567 + /** 1.568 + * @param data 1.569 + * A wrapped object containing raw PDU data. 1.570 + * 1.571 + * @return Decoded string. 1.572 + */ 1.573 + decode: function(data) { 1.574 + let begin = data.offset; 1.575 + let firstCode = Octet.decode(data); 1.576 + if (firstCode == 127) { 1.577 + // Quote found, check if first char code is larger-equal than 128. 1.578 + begin = data.offset; 1.579 + try { 1.580 + if (Octet.decode(data) < 128) { 1.581 + throw new CodeError("Text-string: illegal quote found."); 1.582 + } 1.583 + } catch (e if e instanceof CodeError) { 1.584 + throw e; 1.585 + } catch (e) { 1.586 + throw new CodeError("Text-string: unexpected error."); 1.587 + } 1.588 + } else if (firstCode >= 128) { 1.589 + throw new CodeError("Text-string: invalid char code " + firstCode); 1.590 + } 1.591 + 1.592 + data.offset = begin; 1.593 + return NullTerminatedTexts.decode(data); 1.594 + }, 1.595 + 1.596 + /** 1.597 + * @param data 1.598 + * A wrapped object to store encoded raw data. 1.599 + * @param str 1.600 + * A String to be encoded. 1.601 + * @param asciiOnly 1.602 + * A boolean to decide if it's only allowed to encode ASCII (0 ~ 127). 1.603 + */ 1.604 + encode: function(data, str, asciiOnly) { 1.605 + if (!str) { 1.606 + Octet.encode(data, 0); 1.607 + return; 1.608 + } 1.609 + 1.610 + let firstCharCode = str.charCodeAt(0); 1.611 + if (firstCharCode >= 128) { 1.612 + if (asciiOnly) { 1.613 + throw new CodeError("Text: invalid char code " + code); 1.614 + } 1.615 + 1.616 + Octet.encode(data, 127); 1.617 + } 1.618 + 1.619 + NullTerminatedTexts.encode(data, str, asciiOnly); 1.620 + }, 1.621 +}; 1.622 + 1.623 +/** 1.624 + * Token-text = Token End-of-string 1.625 + * 1.626 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.627 + */ 1.628 +this.TokenText = { 1.629 + /** 1.630 + * @param data 1.631 + * A wrapped object containing raw PDU data. 1.632 + * 1.633 + * @return Decoded string. 1.634 + */ 1.635 + decode: function(data) { 1.636 + let str = ""; 1.637 + try { 1.638 + // A End-of-string is also a CTL, which should cause a error. 1.639 + while (true) { 1.640 + str += Token.decode(data); 1.641 + } 1.642 + } catch (e if e instanceof NullCharError) { 1.643 + return str; 1.644 + } 1.645 + }, 1.646 + 1.647 + /** 1.648 + * @param data 1.649 + * A wrapped object to store encoded raw data. 1.650 + * @param str 1.651 + * A String to be encoded. 1.652 + */ 1.653 + encode: function(data, str) { 1.654 + if (str) { 1.655 + for (let i = 0; i < str.length; i++) { 1.656 + Token.encode(data, str.charAt(i)); 1.657 + } 1.658 + } 1.659 + Octet.encode(data, 0); 1.660 + }, 1.661 +}; 1.662 + 1.663 +/** 1.664 + * The TEXT encodes an RFC2616 Quoted-string with the enclosing 1.665 + * quotation-marks <"> removed. 1.666 + * 1.667 + * Quoted-string = <Octet 34> *TEXT End-of-string 1.668 + * 1.669 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.670 + */ 1.671 +this.QuotedString = { 1.672 + /** 1.673 + * @param data 1.674 + * A wrapped object containing raw PDU data. 1.675 + * 1.676 + * @return Decoded string. 1.677 + * 1.678 + * @throws CodeError if first octet read is not 0x34. 1.679 + */ 1.680 + decode: function(data) { 1.681 + let value = Octet.decode(data); 1.682 + if (value != 34) { 1.683 + throw new CodeError("Quoted-string: not quote " + value); 1.684 + } 1.685 + 1.686 + return NullTerminatedTexts.decode(data); 1.687 + }, 1.688 + 1.689 + /** 1.690 + * @param data 1.691 + * A wrapped object to store encoded raw data. 1.692 + * @param str 1.693 + * A String to be encoded. 1.694 + */ 1.695 + encode: function(data, str) { 1.696 + Octet.encode(data, 34); 1.697 + NullTerminatedTexts.encode(data, str); 1.698 + }, 1.699 +}; 1.700 + 1.701 +/** 1.702 + * Integers in range 0-127 shall be encoded as a one octet value with the 1.703 + * most significant bit set to one (1xxx xxxx) and with the value in the 1.704 + * remaining least significant bits. 1.705 + * 1.706 + * Short-integer = OCTET 1.707 + * 1.708 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.709 + */ 1.710 +this.ShortInteger = { 1.711 + /** 1.712 + * @param data 1.713 + * A wrapped object containing raw PDU data. 1.714 + * 1.715 + * @return Decoded integer value. 1.716 + * 1.717 + * @throws CodeError if the octet read is less than 0x80. 1.718 + */ 1.719 + decode: function(data) { 1.720 + let value = Octet.decode(data); 1.721 + if (!(value & 0x80)) { 1.722 + throw new CodeError("Short-integer: invalid value " + value); 1.723 + } 1.724 + 1.725 + return (value & 0x7F); 1.726 + }, 1.727 + 1.728 + /** 1.729 + * @param data 1.730 + * A wrapped object to store encoded raw data. 1.731 + * @param value 1.732 + * A numeric value to be encoded. 1.733 + * 1.734 + * @throws CodeError if the octet read is larger-equal than 0x80. 1.735 + */ 1.736 + encode: function(data, value) { 1.737 + if (value >= 0x80) { 1.738 + throw new CodeError("Short-integer: invalid value " + value); 1.739 + } 1.740 + 1.741 + Octet.encode(data, value | 0x80); 1.742 + }, 1.743 +}; 1.744 + 1.745 +/** 1.746 + * The content octets shall be an unsigned integer value with the most 1.747 + * significant octet encoded first (big-endian representation). The minimum 1.748 + * number of octets must be used to encode the value. 1.749 + * 1.750 + * Long-integer = Short-length Multi-octet-integer 1.751 + * Short-length = <Any octet 0-30> 1.752 + * Multi-octet-integer = 1*30 OCTET 1.753 + * 1.754 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.755 + */ 1.756 +this.LongInteger = { 1.757 + /** 1.758 + * @param data 1.759 + * A wrapped object containing raw PDU data. 1.760 + * @param length 1.761 + * Number of octets to read. 1.762 + * 1.763 + * @return A decoded integer value or an octets array of max 30 elements. 1.764 + */ 1.765 + decodeMultiOctetInteger: function(data, length) { 1.766 + if (length < 7) { 1.767 + // Return a integer instead of an array as possible. For a multi-octet 1.768 + // integer, there are only maximum 53 bits for integer in javascript. We 1.769 + // will get an inaccurate one beyond that. We can't neither use bitwise 1.770 + // operation here, for it will be limited in 32 bits. 1.771 + let value = 0; 1.772 + while (length--) { 1.773 + value = value * 256 + Octet.decode(data); 1.774 + } 1.775 + return value; 1.776 + } 1.777 + 1.778 + return Octet.decodeMultiple(data, data.offset + length); 1.779 + }, 1.780 + 1.781 + /** 1.782 + * @param data 1.783 + * A wrapped object containing raw PDU data. 1.784 + * 1.785 + * @return A decoded integer value or an octets array of max 30 elements. 1.786 + * 1.787 + * @throws CodeError if the length read is not in 1..30. 1.788 + */ 1.789 + decode: function(data) { 1.790 + let length = Octet.decode(data); 1.791 + if ((length < 1) || (length > 30)) { 1.792 + throw new CodeError("Long-integer: invalid length " + length); 1.793 + } 1.794 + 1.795 + return this.decodeMultiOctetInteger(data, length); 1.796 + }, 1.797 + 1.798 + /** 1.799 + * @param data 1.800 + * A wrapped object to store encoded raw data. 1.801 + * @param numOrArray 1.802 + * An octet array of less-equal than 30 elements or an integer 1.803 + * greater-equal than 128. 1.804 + */ 1.805 + encode: function(data, numOrArray) { 1.806 + if (typeof numOrArray === "number") { 1.807 + let num = numOrArray; 1.808 + if (num >= 0x1000000000000) { 1.809 + throw new CodeError("Long-integer: number too large " + num); 1.810 + } 1.811 + 1.812 + let stack = []; 1.813 + do { 1.814 + stack.push(Math.floor(num % 256)); 1.815 + num = Math.floor(num / 256); 1.816 + } while (num); 1.817 + 1.818 + Octet.encode(data, stack.length); 1.819 + while (stack.length) { 1.820 + Octet.encode(data, stack.pop()); 1.821 + } 1.822 + return; 1.823 + } 1.824 + 1.825 + let array = numOrArray; 1.826 + if ((array.length < 1) || (array.length > 30)) { 1.827 + throw new CodeError("Long-integer: invalid length " + array.length); 1.828 + } 1.829 + 1.830 + Octet.encode(data, array.length); 1.831 + Octet.encodeMultiple(data, array); 1.832 + }, 1.833 +}; 1.834 + 1.835 +/** 1.836 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.837 + */ 1.838 +this.UintVar = { 1.839 + /** 1.840 + * @param data 1.841 + * A wrapped object containing raw PDU data. 1.842 + * 1.843 + * @return Decoded integer value. 1.844 + */ 1.845 + decode: function(data) { 1.846 + let value = Octet.decode(data); 1.847 + let result = value & 0x7F; 1.848 + while (value & 0x80) { 1.849 + value = Octet.decode(data); 1.850 + result = result * 128 + (value & 0x7F); 1.851 + } 1.852 + 1.853 + return result; 1.854 + }, 1.855 + 1.856 + /** 1.857 + * @param data 1.858 + * A wrapped object to store encoded raw data. 1.859 + * @param value 1.860 + * An integer value. 1.861 + */ 1.862 + encode: function(data, value) { 1.863 + if (value < 0) { 1.864 + throw new CodeError("UintVar: invalid value " + value); 1.865 + } 1.866 + 1.867 + let stack = []; 1.868 + while (value >= 128) { 1.869 + stack.push(Math.floor(value % 128)); 1.870 + value = Math.floor(value / 128); 1.871 + } 1.872 + 1.873 + while (stack.length) { 1.874 + Octet.encode(data, value | 0x80); 1.875 + value = stack.pop(); 1.876 + } 1.877 + Octet.encode(data, value); 1.878 + }, 1.879 +}; 1.880 + 1.881 +/** 1.882 + * This encoding is used for token values, which have no well-known binary 1.883 + * encoding, or when the assigned number of the well-known encoding is small 1.884 + * enough to fit into Short-Integer. We change Extension-Media from 1.885 + * NullTerminatedTexts to TextString because of Bug 823816. 1.886 + * 1.887 + * Constrained-encoding = Extension-Media | Short-integer 1.888 + * Extension-Media = TextString 1.889 + * 1.890 + * @see WAP-230-WSP-20010705-a clause 8.4.2.1 1.891 + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=823816 1.892 + */ 1.893 +this.ConstrainedEncoding = { 1.894 + /** 1.895 + * @param data 1.896 + * A wrapped object containing raw PDU data. 1.897 + * 1.898 + * @return Decode integer value or string. 1.899 + */ 1.900 + decode: function(data) { 1.901 + return decodeAlternatives(data, null, TextString, ShortInteger); 1.902 + }, 1.903 + 1.904 + /** 1.905 + * @param data 1.906 + * A wrapped object to store encoded raw data. 1.907 + * @param value 1.908 + * An integer or a string value. 1.909 + */ 1.910 + encode: function(data, value) { 1.911 + if (typeof value == "number") { 1.912 + ShortInteger.encode(data, value); 1.913 + } else { 1.914 + TextString.encode(data, value); 1.915 + } 1.916 + }, 1.917 +}; 1.918 + 1.919 +/** 1.920 + * Value-length = Short-length | (Length-quote Length) 1.921 + * Short-length = <Any octet 0-30> 1.922 + * Length-quote = <Octet 31> 1.923 + * Length = Uintvar-integer 1.924 + * 1.925 + * @see WAP-230-WSP-20010705-a clause 8.4.2.2 1.926 + */ 1.927 +this.ValueLength = { 1.928 + /** 1.929 + * @param data 1.930 + * A wrapped object containing raw PDU data. 1.931 + * 1.932 + * @return Decoded integer value. 1.933 + * 1.934 + * @throws CodeError if the first octet read is larger than 31. 1.935 + */ 1.936 + decode: function(data) { 1.937 + let value = Octet.decode(data); 1.938 + if (value <= 30) { 1.939 + return value; 1.940 + } 1.941 + 1.942 + if (value == 31) { 1.943 + return UintVar.decode(data); 1.944 + } 1.945 + 1.946 + throw new CodeError("Value-length: invalid value " + value); 1.947 + }, 1.948 + 1.949 + /** 1.950 + * @param data 1.951 + * A wrapped object to store encoded raw data. 1.952 + * @param value 1.953 + */ 1.954 + encode: function(data, value) { 1.955 + if (value <= 30) { 1.956 + Octet.encode(data, value); 1.957 + } else { 1.958 + Octet.encode(data, 31); 1.959 + UintVar.encode(data, value); 1.960 + } 1.961 + }, 1.962 +}; 1.963 + 1.964 +/** 1.965 + * No-value = <Octet 0> 1.966 + * 1.967 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.968 + */ 1.969 +this.NoValue = { 1.970 + /** 1.971 + * @param data 1.972 + * A wrapped object containing raw PDU data. 1.973 + * 1.974 + * @return Always returns null. 1.975 + */ 1.976 + decode: function(data) { 1.977 + Octet.decodeEqualTo(data, 0); 1.978 + return null; 1.979 + }, 1.980 + 1.981 + /** 1.982 + * @param data 1.983 + * A wrapped object to store encoded raw data. 1.984 + * @param value 1.985 + * A null or undefined value. 1.986 + */ 1.987 + encode: function(data, value) { 1.988 + if (value != null) { 1.989 + throw new CodeError("No-value: invalid value " + value); 1.990 + } 1.991 + Octet.encode(data, 0); 1.992 + }, 1.993 +}; 1.994 + 1.995 +/** 1.996 + * Text-value = No-value | Token-text | Quoted-string 1.997 + * 1.998 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.999 + */ 1.1000 +this.TextValue = { 1.1001 + /** 1.1002 + * @param data 1.1003 + * A wrapped object containing raw PDU data. 1.1004 + * 1.1005 + * @return Decoded string or null for No-value. 1.1006 + */ 1.1007 + decode: function(data) { 1.1008 + return decodeAlternatives(data, null, NoValue, TokenText, QuotedString); 1.1009 + }, 1.1010 + 1.1011 + /** 1.1012 + * @param data 1.1013 + * A wrapped object to store encoded raw data. 1.1014 + * @param text 1.1015 + * A null or undefined or text string. 1.1016 + */ 1.1017 + encode: function(data, text) { 1.1018 + encodeAlternatives(data, text, null, NoValue, TokenText, QuotedString); 1.1019 + }, 1.1020 +}; 1.1021 + 1.1022 +/** 1.1023 + * Integer-Value = Short-integer | Long-integer 1.1024 + * 1.1025 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1026 + */ 1.1027 +this.IntegerValue = { 1.1028 + /** 1.1029 + * @param data 1.1030 + * A wrapped object containing raw PDU data. 1.1031 + * 1.1032 + * @return Decoded integer value or array of octets. 1.1033 + */ 1.1034 + decode: function(data) { 1.1035 + return decodeAlternatives(data, null, ShortInteger, LongInteger); 1.1036 + }, 1.1037 + 1.1038 + /** 1.1039 + * @param data 1.1040 + * A wrapped object to store encoded raw data. 1.1041 + * @param value 1.1042 + * An integer value or an octet array of less-equal than 31 elements. 1.1043 + */ 1.1044 + encode: function(data, value) { 1.1045 + if (typeof value === "number") { 1.1046 + encodeAlternatives(data, value, null, ShortInteger, LongInteger); 1.1047 + } else if (Array.isArray(value) || (value instanceof Uint8Array)) { 1.1048 + LongInteger.encode(data, value); 1.1049 + } else { 1.1050 + throw new CodeError("Integer-Value: invalid value type"); 1.1051 + } 1.1052 + }, 1.1053 +}; 1.1054 + 1.1055 +/** 1.1056 + * The encoding of dates shall be done in number of seconds from 1.1057 + * 1970-01-01, 00:00:00 GMT. 1.1058 + * 1.1059 + * Date-value = Long-integer 1.1060 + * 1.1061 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1062 + */ 1.1063 +this.DateValue = { 1.1064 + /** 1.1065 + * @param data 1.1066 + * A wrapped object containing raw PDU data. 1.1067 + * 1.1068 + * @return A Date object. 1.1069 + */ 1.1070 + decode: function(data) { 1.1071 + let numOrArray = LongInteger.decode(data); 1.1072 + let seconds; 1.1073 + if (typeof numOrArray == "number") { 1.1074 + seconds = numOrArray; 1.1075 + } else { 1.1076 + seconds = 0; 1.1077 + for (let i = 0; i < numOrArray.length; i++) { 1.1078 + seconds = seconds * 256 + numOrArray[i]; 1.1079 + } 1.1080 + } 1.1081 + 1.1082 + return new Date(seconds * 1000); 1.1083 + }, 1.1084 + 1.1085 + /** 1.1086 + * @param data 1.1087 + * A wrapped object to store encoded raw data. 1.1088 + * @param date 1.1089 + * A Date object. 1.1090 + */ 1.1091 + encode: function(data, date) { 1.1092 + let seconds = date.getTime() / 1000; 1.1093 + if (seconds < 0) { 1.1094 + throw new CodeError("Date-value: negative seconds " + seconds); 1.1095 + } 1.1096 + 1.1097 + LongInteger.encode(data, seconds); 1.1098 + }, 1.1099 +}; 1.1100 + 1.1101 +/** 1.1102 + * Delta-seconds-value = Integer-value 1.1103 + * 1.1104 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1105 + */ 1.1106 +this.DeltaSecondsValue = IntegerValue; 1.1107 + 1.1108 +/** 1.1109 + * Quality factor 0 and quality factors with one or two decimal digits are 1.1110 + * encoded into 1-100; three digits ones into 101-1099. 1.1111 + * 1.1112 + * Q-value = 1*2 OCTET 1.1113 + * 1.1114 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1115 + */ 1.1116 +this.QValue = { 1.1117 + /** 1.1118 + * @param data 1.1119 + * A wrapped object containing raw PDU data. 1.1120 + * 1.1121 + * @return Decoded integer value of 1..1099. 1.1122 + * 1.1123 + * @throws CodeError if decoded UintVar is not in range 1..1099. 1.1124 + */ 1.1125 + decode: function(data) { 1.1126 + let value = UintVar.decode(data); 1.1127 + if (value > 0) { 1.1128 + if (value <= 100) { 1.1129 + return (value - 1) / 100.0; 1.1130 + } 1.1131 + if (value <= 1099) { 1.1132 + return (value - 100) / 1000.0; 1.1133 + } 1.1134 + } 1.1135 + 1.1136 + throw new CodeError("Q-value: invalid value " + value); 1.1137 + }, 1.1138 + 1.1139 + /** 1.1140 + * @param data 1.1141 + * A wrapped object to store encoded raw data. 1.1142 + * @param value 1.1143 + * An integer within the range 1..1099. 1.1144 + */ 1.1145 + encode: function(data, value) { 1.1146 + if ((value < 0) || (value >= 1)) { 1.1147 + throw new CodeError("Q-value: invalid value " + value); 1.1148 + } 1.1149 + 1.1150 + value *= 1000; 1.1151 + if ((value % 10) == 0) { 1.1152 + // Two digits only. 1.1153 + UintVar.encode(data, Math.floor(value / 10 + 1)); 1.1154 + } else { 1.1155 + // Three digits. 1.1156 + UintVar.encode(data, Math.floor(value + 100)); 1.1157 + } 1.1158 + }, 1.1159 +}; 1.1160 + 1.1161 +/** 1.1162 + * The three most significant bits of the Short-integer value are interpreted 1.1163 + * to encode a major version number in the range 1-7, and the four least 1.1164 + * significant bits contain a minor version number in the range 0-14. If 1.1165 + * there is only a major version number, this is encoded by placing the value 1.1166 + * 15 in the four least significant bits. 1.1167 + * 1.1168 + * Version-value = Short-integer | Text-string 1.1169 + * 1.1170 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1171 + */ 1.1172 +this.VersionValue = { 1.1173 + /** 1.1174 + * @param data 1.1175 + * A wrapped object containing raw PDU data. 1.1176 + * 1.1177 + * @return Binary encoded version number. 1.1178 + */ 1.1179 + decode: function(data) { 1.1180 + let begin = data.offset; 1.1181 + let value; 1.1182 + try { 1.1183 + value = ShortInteger.decode(data); 1.1184 + if ((value >= 0x10) && (value < 0x80)) { 1.1185 + return value; 1.1186 + } 1.1187 + 1.1188 + throw new CodeError("Version-value: invalid value " + value); 1.1189 + } catch (e) {} 1.1190 + 1.1191 + data.offset = begin; 1.1192 + 1.1193 + let str = TextString.decode(data); 1.1194 + if (!str.match(/^[1-7](\.1?\d)?$/)) { 1.1195 + throw new CodeError("Version-value: invalid value " + str); 1.1196 + } 1.1197 + 1.1198 + let major = str.charCodeAt(0) - 0x30; 1.1199 + let minor = 0x0F; 1.1200 + if (str.length > 1) { 1.1201 + minor = str.charCodeAt(2) - 0x30; 1.1202 + if (str.length > 3) { 1.1203 + minor = 10 + (str.charCodeAt(3) - 0x30); 1.1204 + if (minor > 14) { 1.1205 + throw new CodeError("Version-value: invalid minor " + minor); 1.1206 + } 1.1207 + } 1.1208 + } 1.1209 + 1.1210 + return major << 4 | minor; 1.1211 + }, 1.1212 + 1.1213 + /** 1.1214 + * @param data 1.1215 + * A wrapped object to store encoded raw data. 1.1216 + * @param version 1.1217 + * A binary encoded version number. 1.1218 + */ 1.1219 + encode: function(data, version) { 1.1220 + if ((version < 0x10) || (version >= 0x80)) { 1.1221 + throw new CodeError("Version-value: invalid version " + version); 1.1222 + } 1.1223 + 1.1224 + ShortInteger.encode(data, version); 1.1225 + }, 1.1226 +}; 1.1227 + 1.1228 +/** 1.1229 + * URI value should be encoded per [RFC2616], but service user may use a 1.1230 + * different format. 1.1231 + * 1.1232 + * Uri-value = Text-string 1.1233 + * 1.1234 + * @see WAP-230-WSP-20010705-a clause 8.4.2.3 1.1235 + * @see RFC 2616 clause 2.2 Basic Rules 1.1236 + */ 1.1237 +this.UriValue = { 1.1238 + /** 1.1239 + * @param data 1.1240 + * A wrapped object containing raw PDU data. 1.1241 + * 1.1242 + * @return Decoded uri string. 1.1243 + */ 1.1244 + decode: function(data) { 1.1245 + let str = ""; 1.1246 + try { 1.1247 + // A End-of-string is also a CTL, which should cause a error. 1.1248 + while (true) { 1.1249 + str += URIC.decode(data); 1.1250 + } 1.1251 + } catch (e if e instanceof NullCharError) { 1.1252 + return str; 1.1253 + } 1.1254 + }, 1.1255 +}; 1.1256 + 1.1257 +/** 1.1258 + * Internal coder for "type" parameter. 1.1259 + * 1.1260 + * Type-value = Constrained-encoding 1.1261 + * 1.1262 + * @see WAP-230-WSP-20010705-a table 38 1.1263 + */ 1.1264 +this.TypeValue = { 1.1265 + /** 1.1266 + * @param data 1.1267 + * A wrapped object containing raw PDU data. 1.1268 + * 1.1269 + * @return Decoded content type string. 1.1270 + */ 1.1271 + decode: function(data) { 1.1272 + let numOrStr = ConstrainedEncoding.decode(data); 1.1273 + if (typeof numOrStr == "string") { 1.1274 + return numOrStr.toLowerCase(); 1.1275 + } 1.1276 + 1.1277 + let number = numOrStr; 1.1278 + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; 1.1279 + if (!entry) { 1.1280 + throw new NotWellKnownEncodingError( 1.1281 + "Constrained-media: not well known media " + number); 1.1282 + } 1.1283 + 1.1284 + return entry.type; 1.1285 + }, 1.1286 + 1.1287 + /** 1.1288 + * @param data 1.1289 + * A wrapped object to store encoded raw data. 1.1290 + * @param type 1.1291 + * A content type string. 1.1292 + */ 1.1293 + encode: function(data, type) { 1.1294 + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[type.toLowerCase()]; 1.1295 + if (entry) { 1.1296 + ConstrainedEncoding.encode(data, entry.number); 1.1297 + } else { 1.1298 + ConstrainedEncoding.encode(data, type); 1.1299 + } 1.1300 + }, 1.1301 +}; 1.1302 + 1.1303 +/** 1.1304 + * Parameter = Typed-parameter | Untyped-parameter 1.1305 + * 1.1306 + * For Typed-parameters, the actual expected type of the value is implied by 1.1307 + * the well-known parameter. In addition to the expected type, there may be no 1.1308 + * value. If the value cannot be encoded using expected type, it shall be 1.1309 + * encoded as text. 1.1310 + * 1.1311 + * Typed-parameter = Well-known-parameter-token Typed-value 1.1312 + * Well-known-parameter-token = Integer-value 1.1313 + * Typed-value = Compact-value | Text-value 1.1314 + * Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value 1.1315 + * | Version-value | Uri-value 1.1316 + * 1.1317 + * For Untyped-parameters, the type of the value is unknown, but is shall be 1.1318 + * encoded as an integer, if that is possible. 1.1319 + * 1.1320 + * Untyped-parameter = Token-text Untyped-value 1.1321 + * Untyped-value = Integer-value | Text-value 1.1322 + * 1.1323 + * @see WAP-230-WSP-20010705-a clause 8.4.2.4 1.1324 + */ 1.1325 +this.Parameter = { 1.1326 + /** 1.1327 + * @param data 1.1328 + * A wrapped object containing raw PDU data. 1.1329 + * 1.1330 + * @return A decoded object containing `name` and `value` properties or null 1.1331 + * if something wrong. The `name` property must be a string, but the 1.1332 + * `value` property can be many different types depending on `name`. 1.1333 + * 1.1334 + * @throws CodeError if decoded IntegerValue is an array. 1.1335 + * @throws NotWellKnownEncodingError if decoded well-known parameter number 1.1336 + * is not registered or supported. 1.1337 + */ 1.1338 + decodeTypedParameter: function(data) { 1.1339 + let numOrArray = IntegerValue.decode(data); 1.1340 + // `decodeIntegerValue` can return a array, which doesn't apply here. 1.1341 + if (typeof numOrArray != "number") { 1.1342 + throw new CodeError("Typed-parameter: invalid integer type"); 1.1343 + } 1.1344 + 1.1345 + let number = numOrArray; 1.1346 + let param = WSP_WELL_KNOWN_PARAMS[number]; 1.1347 + if (!param) { 1.1348 + throw new NotWellKnownEncodingError( 1.1349 + "Typed-parameter: not well known parameter " + number); 1.1350 + } 1.1351 + 1.1352 + let begin = data.offset, value; 1.1353 + try { 1.1354 + // Althought Text-string is not included in BNF of Compact-value, but 1.1355 + // some service provider might still pass a less-strict text form and 1.1356 + // cause a unexpected CodeError raised. For example, the `start` 1.1357 + // parameter expects its value of Text-value, but service provider might 1.1358 + // gives "<smil>", which contains illegal characters "<" and ">". 1.1359 + value = decodeAlternatives(data, null, 1.1360 + param.coder, TextValue, TextString); 1.1361 + } catch (e) { 1.1362 + data.offset = begin; 1.1363 + 1.1364 + // Skip current parameter. 1.1365 + value = skipValue(data); 1.1366 + debug("Skip malformed typed parameter: " 1.1367 + + JSON.stringify({name: param.name, value: value})); 1.1368 + 1.1369 + return null; 1.1370 + } 1.1371 + 1.1372 + return { 1.1373 + name: param.name, 1.1374 + value: value, 1.1375 + }; 1.1376 + }, 1.1377 + 1.1378 + /** 1.1379 + * @param data 1.1380 + * A wrapped object containing raw PDU data. 1.1381 + * 1.1382 + * @return A decoded object containing `name` and `value` properties or null 1.1383 + * if something wrong. The `name` property must be a string, but the 1.1384 + * `value` property can be many different types depending on `name`. 1.1385 + */ 1.1386 + decodeUntypedParameter: function(data) { 1.1387 + let name = TokenText.decode(data); 1.1388 + 1.1389 + let begin = data.offset, value; 1.1390 + try { 1.1391 + value = decodeAlternatives(data, null, IntegerValue, TextValue); 1.1392 + } catch (e) { 1.1393 + data.offset = begin; 1.1394 + 1.1395 + // Skip current parameter. 1.1396 + value = skipValue(data); 1.1397 + debug("Skip malformed untyped parameter: " 1.1398 + + JSON.stringify({name: name, value: value})); 1.1399 + 1.1400 + return null; 1.1401 + } 1.1402 + 1.1403 + return { 1.1404 + name: name.toLowerCase(), 1.1405 + value: value, 1.1406 + }; 1.1407 + }, 1.1408 + 1.1409 + /** 1.1410 + * @param data 1.1411 + * A wrapped object containing raw PDU data. 1.1412 + * 1.1413 + * @return A decoded object containing `name` and `value` properties or null 1.1414 + * if something wrong. The `name` property must be a string, but the 1.1415 + * `value` property can be many different types depending on `name`. 1.1416 + */ 1.1417 + decode: function(data) { 1.1418 + let begin = data.offset; 1.1419 + try { 1.1420 + return this.decodeTypedParameter(data); 1.1421 + } catch (e) { 1.1422 + data.offset = begin; 1.1423 + return this.decodeUntypedParameter(data); 1.1424 + } 1.1425 + }, 1.1426 + 1.1427 + /** 1.1428 + * @param data 1.1429 + * A wrapped object containing raw PDU data. 1.1430 + * @param end 1.1431 + * Ending offset of following parameters. 1.1432 + * 1.1433 + * @return An array of decoded objects. 1.1434 + */ 1.1435 + decodeMultiple: function(data, end) { 1.1436 + let params = null, param; 1.1437 + 1.1438 + while (data.offset < end) { 1.1439 + try { 1.1440 + param = this.decode(data); 1.1441 + } catch (e) { 1.1442 + break; 1.1443 + } 1.1444 + if (param) { 1.1445 + if (!params) { 1.1446 + params = {}; 1.1447 + } 1.1448 + params[param.name] = param.value; 1.1449 + } 1.1450 + } 1.1451 + 1.1452 + return params; 1.1453 + }, 1.1454 + 1.1455 + /** 1.1456 + * @param data 1.1457 + * A wrapped object to store encoded raw data. 1.1458 + * @param param 1.1459 + * An object containing `name` and `value` properties. 1.1460 + */ 1.1461 + encodeTypedParameter: function(data, param) { 1.1462 + let entry = WSP_WELL_KNOWN_PARAMS[param.name.toLowerCase()]; 1.1463 + if (!entry) { 1.1464 + throw new NotWellKnownEncodingError( 1.1465 + "Typed-parameter: not well known parameter " + param.name); 1.1466 + } 1.1467 + 1.1468 + IntegerValue.encode(data, entry.number); 1.1469 + encodeAlternatives(data, param.value, null, 1.1470 + entry.coder, TextValue, TextString); 1.1471 + }, 1.1472 + 1.1473 + /** 1.1474 + * @param data 1.1475 + * A wrapped object to store encoded raw data. 1.1476 + * @param param 1.1477 + * An object containing `name` and `value` properties. 1.1478 + */ 1.1479 + encodeUntypedParameter: function(data, param) { 1.1480 + TokenText.encode(data, param.name); 1.1481 + encodeAlternatives(data, param.value, null, IntegerValue, TextValue); 1.1482 + }, 1.1483 + 1.1484 + /** 1.1485 + * @param data 1.1486 + * A wrapped object to store encoded raw data. 1.1487 + * @param param 1.1488 + * An array of parameter objects. 1.1489 + */ 1.1490 + encodeMultiple: function(data, params) { 1.1491 + for (let name in params) { 1.1492 + this.encode(data, {name: name, value: params[name]}); 1.1493 + } 1.1494 + }, 1.1495 + 1.1496 + /** 1.1497 + * @param data 1.1498 + * A wrapped object to store encoded raw data. 1.1499 + * @param param 1.1500 + * An object containing `name` and `value` properties. 1.1501 + */ 1.1502 + encode: function(data, param) { 1.1503 + let begin = data.offset; 1.1504 + try { 1.1505 + this.encodeTypedParameter(data, param); 1.1506 + } catch (e) { 1.1507 + data.offset = begin; 1.1508 + this.encodeUntypedParameter(data, param); 1.1509 + } 1.1510 + }, 1.1511 +}; 1.1512 + 1.1513 +/** 1.1514 + * Header = Message-header | Shift-sequence 1.1515 + * Message-header = Well-known-header | Application-header 1.1516 + * 1.1517 + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 1.1518 + */ 1.1519 +this.Header = { 1.1520 + /** 1.1521 + * @param data 1.1522 + * A wrapped object containing raw PDU data. 1.1523 + * 1.1524 + * @return A decoded object containing `name` and `value` properties or null 1.1525 + * in case of a failed parsing. The `name` property must be a string, 1.1526 + * but the `value` property can be many different types depending on 1.1527 + * `name`. 1.1528 + */ 1.1529 + decodeMessageHeader: function(data) { 1.1530 + return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader); 1.1531 + }, 1.1532 + 1.1533 + /** 1.1534 + * @param data 1.1535 + * A wrapped object containing raw PDU data. 1.1536 + * 1.1537 + * @return A decoded object containing `name` and `value` properties or null 1.1538 + * in case of a failed parsing. The `name` property must be a string, 1.1539 + * but the `value` property can be many different types depending on 1.1540 + * `name`. 1.1541 + */ 1.1542 + decode: function(data) { 1.1543 + // TODO: support header code page shift-sequence 1.1544 + return this.decodeMessageHeader(data); 1.1545 + }, 1.1546 + 1.1547 + encodeMessageHeader: function(data, header) { 1.1548 + encodeAlternatives(data, header, null, WellKnownHeader, ApplicationHeader); 1.1549 + }, 1.1550 + 1.1551 + /** 1.1552 + * @param data 1.1553 + * A wrapped object to store encoded raw data. 1.1554 + * @param header 1.1555 + * An object containing two attributes: a string-typed `name` and a 1.1556 + * `value` of arbitrary type. 1.1557 + */ 1.1558 + encode: function(data, header) { 1.1559 + // TODO: support header code page shift-sequence 1.1560 + this.encodeMessageHeader(data, header); 1.1561 + }, 1.1562 +}; 1.1563 + 1.1564 +/** 1.1565 + * Well-known-header = Well-known-field-name Wap-value 1.1566 + * Well-known-field-name = Short-integer 1.1567 + * 1.1568 + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 1.1569 + */ 1.1570 +this.WellKnownHeader = { 1.1571 + /** 1.1572 + * @param data 1.1573 + * A wrapped object containing raw PDU data. 1.1574 + * 1.1575 + * @return A decoded object containing `name` and `value` properties or null 1.1576 + * in case of a failed parsing. The `name` property must be a string, 1.1577 + * but the `value` property can be many different types depending on 1.1578 + * `name`. 1.1579 + * 1.1580 + * @throws NotWellKnownEncodingError if decoded well-known header field 1.1581 + * number is not registered or supported. 1.1582 + */ 1.1583 + decode: function(data) { 1.1584 + let index = ShortInteger.decode(data); 1.1585 + 1.1586 + let entry = WSP_HEADER_FIELDS[index]; 1.1587 + if (!entry) { 1.1588 + throw new NotWellKnownEncodingError( 1.1589 + "Well-known-header: not well known header " + index); 1.1590 + } 1.1591 + 1.1592 + let begin = data.offset, value; 1.1593 + try { 1.1594 + value = decodeAlternatives(data, null, entry.coder, TextValue); 1.1595 + } catch (e) { 1.1596 + data.offset = begin; 1.1597 + 1.1598 + value = skipValue(data); 1.1599 + debug("Skip malformed well known header(" + index + "): " 1.1600 + + JSON.stringify({name: entry.name, value: value})); 1.1601 + 1.1602 + return null; 1.1603 + } 1.1604 + 1.1605 + return { 1.1606 + name: entry.name, 1.1607 + value: value, 1.1608 + }; 1.1609 + }, 1.1610 + 1.1611 + /** 1.1612 + * @param data 1.1613 + * A wrapped object to store encoded raw data. 1.1614 + * @param header 1.1615 + * An object containing two attributes: a string-typed `name` and a 1.1616 + * `value` of arbitrary type. 1.1617 + */ 1.1618 + encode: function(data, header) { 1.1619 + let entry = WSP_HEADER_FIELDS[header.name.toLowerCase()]; 1.1620 + if (!entry) { 1.1621 + throw new NotWellKnownEncodingError( 1.1622 + "Well-known-header: not well known header " + header.name); 1.1623 + } 1.1624 + 1.1625 + ShortInteger.encode(data, entry.number); 1.1626 + encodeAlternatives(data, header.value, null, entry.coder, TextValue); 1.1627 + }, 1.1628 +}; 1.1629 + 1.1630 +/** 1.1631 + * Application-header = Token-text Application-specific-value 1.1632 + * Application-specific-value = Text-string 1.1633 + * 1.1634 + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 1.1635 + */ 1.1636 +this.ApplicationHeader = { 1.1637 + /** 1.1638 + * @param data 1.1639 + * A wrapped object containing raw PDU data. 1.1640 + * 1.1641 + * @return A decoded object containing `name` and `value` properties or null 1.1642 + * in case of a failed parsing. The `name` property must be a string, 1.1643 + * but the `value` property can be many different types depending on 1.1644 + * `name`. 1.1645 + */ 1.1646 + decode: function(data) { 1.1647 + let name = TokenText.decode(data); 1.1648 + 1.1649 + let begin = data.offset, value; 1.1650 + try { 1.1651 + value = TextString.decode(data); 1.1652 + } catch (e) { 1.1653 + data.offset = begin; 1.1654 + 1.1655 + value = skipValue(data); 1.1656 + debug("Skip malformed application header: " 1.1657 + + JSON.stringify({name: name, value: value})); 1.1658 + 1.1659 + return null; 1.1660 + } 1.1661 + 1.1662 + return { 1.1663 + name: name.toLowerCase(), 1.1664 + value: value, 1.1665 + }; 1.1666 + }, 1.1667 + 1.1668 + /** 1.1669 + * @param data 1.1670 + * A wrapped object to store encoded raw data. 1.1671 + * @param header 1.1672 + * An object containing two attributes: a string-typed `name` and a 1.1673 + * `value` of arbitrary type. 1.1674 + * 1.1675 + * @throws CodeError if got an empty header name. 1.1676 + */ 1.1677 + encode: function(data, header) { 1.1678 + if (!header.name) { 1.1679 + throw new CodeError("Application-header: empty header name"); 1.1680 + } 1.1681 + 1.1682 + TokenText.encode(data, header.name); 1.1683 + TextString.encode(data, header.value); 1.1684 + }, 1.1685 +}; 1.1686 + 1.1687 +/** 1.1688 + * Field-name = Token-text | Well-known-field-name 1.1689 + * Well-known-field-name = Short-integer 1.1690 + * 1.1691 + * @see WAP-230-WSP-20010705-a clause 8.4.2.6 1.1692 + */ 1.1693 +this.FieldName = { 1.1694 + /** 1.1695 + * @param data 1.1696 + * A wrapped object containing raw PDU data. 1.1697 + * 1.1698 + * @return A field name string. 1.1699 + * 1.1700 + * @throws NotWellKnownEncodingError if decoded well-known header field 1.1701 + * number is not registered or supported. 1.1702 + */ 1.1703 + decode: function(data) { 1.1704 + let begin = data.offset; 1.1705 + try { 1.1706 + return TokenText.decode(data).toLowerCase(); 1.1707 + } catch (e) {} 1.1708 + 1.1709 + data.offset = begin; 1.1710 + 1.1711 + let number = ShortInteger.decode(data); 1.1712 + let entry = WSP_HEADER_FIELDS[number]; 1.1713 + if (!entry) { 1.1714 + throw new NotWellKnownEncodingError( 1.1715 + "Field-name: not well known encoding " + number); 1.1716 + } 1.1717 + 1.1718 + return entry.name; 1.1719 + }, 1.1720 + 1.1721 + /** 1.1722 + * @param data 1.1723 + * A wrapped object to store encoded raw data. 1.1724 + * @param name 1.1725 + * A field name string. 1.1726 + */ 1.1727 + encode: function(data, name) { 1.1728 + let entry = WSP_HEADER_FIELDS[name.toLowerCase()]; 1.1729 + if (entry) { 1.1730 + ShortInteger.encode(data, entry.number); 1.1731 + } else { 1.1732 + TokenText.encode(data, name); 1.1733 + } 1.1734 + }, 1.1735 +}; 1.1736 + 1.1737 +/** 1.1738 + * Accept-charset-value = Constrained-charset | Accept-charset-general-form 1.1739 + * Constrained-charset = Any-charset | Constrained-encoding 1.1740 + * Any-charset = <Octet 128> 1.1741 + * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value] 1.1742 + * 1.1743 + * @see WAP-230-WSP-20010705-a clause 8.4.2.8 1.1744 + */ 1.1745 +this.AcceptCharsetValue = { 1.1746 + /** 1.1747 + * @param data 1.1748 + * A wrapped object containing raw PDU data. 1.1749 + * 1.1750 + * @return A object with a property `charset` of string "*". 1.1751 + */ 1.1752 + decodeAnyCharset: function(data) { 1.1753 + Octet.decodeEqualTo(data, 128); 1.1754 + return {charset: "*"}; 1.1755 + }, 1.1756 + 1.1757 + /** 1.1758 + * @param data 1.1759 + * A wrapped object containing raw PDU data. 1.1760 + * 1.1761 + * @return A object with a string property `charset` and a optional integer 1.1762 + * property `q`. 1.1763 + * 1.1764 + * @throws NotWellKnownEncodingError if decoded well-known charset number is 1.1765 + * not registered or supported. 1.1766 + */ 1.1767 + decodeConstrainedCharset: function(data) { 1.1768 + let begin = data.offset; 1.1769 + try { 1.1770 + return this.decodeAnyCharset(data); 1.1771 + } catch (e) {} 1.1772 + 1.1773 + data.offset = begin; 1.1774 + 1.1775 + let numOrStr = ConstrainedEncoding.decode(data); 1.1776 + if (typeof numOrStr == "string") { 1.1777 + return {charset: numOrStr}; 1.1778 + } 1.1779 + 1.1780 + let charset = numOrStr; 1.1781 + let entry = WSP_WELL_KNOWN_CHARSETS[charset]; 1.1782 + if (!entry) { 1.1783 + throw new NotWellKnownEncodingError( 1.1784 + "Constrained-charset: not well known charset: " + charset); 1.1785 + } 1.1786 + 1.1787 + return {charset: entry.name}; 1.1788 + }, 1.1789 + 1.1790 + /** 1.1791 + * @param data 1.1792 + * A wrapped object containing raw PDU data. 1.1793 + * 1.1794 + * @return A object with a string property `charset` and a optional integer 1.1795 + * property `q`. 1.1796 + */ 1.1797 + decodeAcceptCharsetGeneralForm: function(data) { 1.1798 + let length = ValueLength.decode(data); 1.1799 + 1.1800 + let begin = data.offset; 1.1801 + let end = begin + length; 1.1802 + 1.1803 + let result; 1.1804 + try { 1.1805 + result = WellKnownCharset.decode(data); 1.1806 + } catch (e) { 1.1807 + data.offset = begin; 1.1808 + 1.1809 + result = {charset: TokenText.decode(data)}; 1.1810 + if (data.offset < end) { 1.1811 + result.q = QValue.decode(data); 1.1812 + } 1.1813 + } 1.1814 + 1.1815 + if (data.offset != end) { 1.1816 + data.offset = end; 1.1817 + } 1.1818 + 1.1819 + return result; 1.1820 + }, 1.1821 + 1.1822 + /** 1.1823 + * @param data 1.1824 + * A wrapped object containing raw PDU data. 1.1825 + * 1.1826 + * @return A object with a string property `charset` and a optional integer 1.1827 + * property `q`. 1.1828 + */ 1.1829 + decode: function(data) { 1.1830 + let begin = data.offset; 1.1831 + try { 1.1832 + return this.decodeConstrainedCharset(data); 1.1833 + } catch (e) { 1.1834 + data.offset = begin; 1.1835 + return this.decodeAcceptCharsetGeneralForm(data); 1.1836 + } 1.1837 + }, 1.1838 + 1.1839 + /** 1.1840 + * @param data 1.1841 + * A wrapped object to store encoded raw data. 1.1842 + * @param value 1.1843 + * An object with a string property `charset`. 1.1844 + */ 1.1845 + encodeAnyCharset: function(data, value) { 1.1846 + if (!value || !value.charset || (value.charset === "*")) { 1.1847 + Octet.encode(data, 128); 1.1848 + return; 1.1849 + } 1.1850 + 1.1851 + throw new CodeError("Any-charset: invalid value " + value); 1.1852 + }, 1.1853 +}; 1.1854 + 1.1855 +/** 1.1856 + * Well-known-charset = Any-charset | Integer-value 1.1857 + * 1.1858 + * @see WAP-230-WSP-20010705-a clause 8.4.2.8 1.1859 + */ 1.1860 +this.WellKnownCharset = { 1.1861 + /** 1.1862 + * @param data 1.1863 + * A wrapped object containing raw PDU data. 1.1864 + * 1.1865 + * @return A object with a string property `charset`. 1.1866 + * 1.1867 + * @throws CodeError if decoded charset number is an array. 1.1868 + * @throws NotWellKnownEncodingError if decoded well-known charset number 1.1869 + * is not registered or supported. 1.1870 + */ 1.1871 + decode: function(data) { 1.1872 + let begin = data.offset; 1.1873 + 1.1874 + try { 1.1875 + return AcceptCharsetValue.decodeAnyCharset(data); 1.1876 + } catch (e) {} 1.1877 + 1.1878 + data.offset = begin; 1.1879 + 1.1880 + // `IntegerValue.decode` can return a array, which doesn't apply here. 1.1881 + let numOrArray = IntegerValue.decode(data); 1.1882 + if (typeof numOrArray != "number") { 1.1883 + throw new CodeError("Well-known-charset: invalid integer type"); 1.1884 + } 1.1885 + 1.1886 + let charset = numOrArray; 1.1887 + let entry = WSP_WELL_KNOWN_CHARSETS[charset]; 1.1888 + if (!entry) { 1.1889 + throw new NotWellKnownEncodingError( 1.1890 + "Well-known-charset: not well known charset " + charset); 1.1891 + } 1.1892 + 1.1893 + return {charset: entry.name}; 1.1894 + }, 1.1895 + 1.1896 + /** 1.1897 + * @param data 1.1898 + * A wrapped object to store encoded raw data. 1.1899 + * @param value 1.1900 + */ 1.1901 + encode: function(data, value) { 1.1902 + let begin = data.offset; 1.1903 + try { 1.1904 + AcceptCharsetValue.encodeAnyCharset(data, value); 1.1905 + return; 1.1906 + } catch (e) {} 1.1907 + 1.1908 + data.offset = begin; 1.1909 + let entry = WSP_WELL_KNOWN_CHARSETS[value.charset.toLowerCase()]; 1.1910 + if (!entry) { 1.1911 + throw new NotWellKnownEncodingError( 1.1912 + "Well-known-charset: not well known charset " + value.charset); 1.1913 + } 1.1914 + 1.1915 + IntegerValue.encode(data, entry.number); 1.1916 + }, 1.1917 +}; 1.1918 + 1.1919 +/** 1.1920 + * The short form of the Content-type-value MUST only be used when the 1.1921 + * well-known media is in the range of 0-127 or a text string. In all other 1.1922 + * cases the general form MUST be used. 1.1923 + * 1.1924 + * Content-type-value = Constrained-media | Content-general-form 1.1925 + * Constrained-media = Constrained-encoding 1.1926 + * Content-general-form = Value-length Media-type 1.1927 + * Media-type = Media *(Parameter) 1.1928 + * Media = Well-known-media | Extension-Media 1.1929 + * Well-known-media = Integer-value 1.1930 + * Extension-Media = *TEXT End-of-string 1.1931 + * 1.1932 + * @see WAP-230-WSP-20010705-a clause 8.4.2.24 1.1933 + */ 1.1934 +this.ContentTypeValue = { 1.1935 + /** 1.1936 + * @param data 1.1937 + * A wrapped object containing raw PDU data. 1.1938 + * 1.1939 + * @return A decoded object containing `media` and `params` properties or 1.1940 + * null in case of a failed parsing. The `media` property must be a 1.1941 + * string, and the `params` property is always null. 1.1942 + * 1.1943 + * @throws NotWellKnownEncodingError if decoded well-known content type number 1.1944 + * is not registered or supported. 1.1945 + */ 1.1946 + decodeConstrainedMedia: function(data) { 1.1947 + return { 1.1948 + media: TypeValue.decode(data), 1.1949 + params: null, 1.1950 + }; 1.1951 + }, 1.1952 + 1.1953 + /** 1.1954 + * @param data 1.1955 + * A wrapped object containing raw PDU data. 1.1956 + * 1.1957 + * @return Decode string. 1.1958 + * 1.1959 + * @throws CodeError if decoded content type number is an array. 1.1960 + * @throws NotWellKnownEncodingError if decoded well-known content type 1.1961 + * number is not registered or supported. 1.1962 + */ 1.1963 + decodeMedia: function(data) { 1.1964 + let begin = data.offset, number; 1.1965 + try { 1.1966 + number = IntegerValue.decode(data); 1.1967 + } catch (e) { 1.1968 + data.offset = begin; 1.1969 + return NullTerminatedTexts.decode(data).toLowerCase(); 1.1970 + } 1.1971 + 1.1972 + // `decodeIntegerValue` can return a array, which doesn't apply here. 1.1973 + if (typeof number != "number") { 1.1974 + throw new CodeError("Media: invalid integer type"); 1.1975 + } 1.1976 + 1.1977 + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number]; 1.1978 + if (!entry) { 1.1979 + throw new NotWellKnownEncodingError("Media: not well known media " + number); 1.1980 + } 1.1981 + 1.1982 + return entry.type; 1.1983 + }, 1.1984 + 1.1985 + /** 1.1986 + * @param data 1.1987 + * A wrapped object containing raw PDU data. 1.1988 + * @param end 1.1989 + * Ending offset of the Media-type value. 1.1990 + * 1.1991 + * @return A decoded object containing `media` and `params` properties or 1.1992 + * null in case of a failed parsing. The `media` property must be a 1.1993 + * string, and the `params` property is a hash map from a string to 1.1994 + * an value of unspecified type. 1.1995 + */ 1.1996 + decodeMediaType: function(data, end) { 1.1997 + let media = this.decodeMedia(data); 1.1998 + let params = Parameter.decodeMultiple(data, end); 1.1999 + 1.2000 + return { 1.2001 + media: media, 1.2002 + params: params, 1.2003 + }; 1.2004 + }, 1.2005 + 1.2006 + /** 1.2007 + * @param data 1.2008 + * A wrapped object containing raw PDU data. 1.2009 + * 1.2010 + * @return A decoded object containing `media` and `params` properties or 1.2011 + * null in case of a failed parsing. The `media` property must be a 1.2012 + * string, and the `params` property is null or a hash map from a 1.2013 + * string to an value of unspecified type. 1.2014 + */ 1.2015 + decodeContentGeneralForm: function(data) { 1.2016 + let length = ValueLength.decode(data); 1.2017 + let end = data.offset + length; 1.2018 + 1.2019 + let value = this.decodeMediaType(data, end); 1.2020 + 1.2021 + if (data.offset != end) { 1.2022 + data.offset = end; 1.2023 + } 1.2024 + 1.2025 + return value; 1.2026 + }, 1.2027 + 1.2028 + /** 1.2029 + * @param data 1.2030 + * A wrapped object containing raw PDU data. 1.2031 + * 1.2032 + * @return A decoded object containing `media` and `params` properties or 1.2033 + * null in case of a failed parsing. The `media` property must be a 1.2034 + * string, and the `params` property is null or a hash map from a 1.2035 + * string to an value of unspecified type. 1.2036 + */ 1.2037 + decode: function(data) { 1.2038 + let begin = data.offset; 1.2039 + 1.2040 + try { 1.2041 + return this.decodeConstrainedMedia(data); 1.2042 + } catch (e) { 1.2043 + data.offset = begin; 1.2044 + return this.decodeContentGeneralForm(data); 1.2045 + } 1.2046 + }, 1.2047 + 1.2048 + /** 1.2049 + * @param data 1.2050 + * A wrapped object to store encoded raw data. 1.2051 + * @param value 1.2052 + * An object containing `media` and `params` properties. 1.2053 + */ 1.2054 + encodeConstrainedMedia: function(data, value) { 1.2055 + if (value.params) { 1.2056 + throw new CodeError("Constrained-media: should use general form instead"); 1.2057 + } 1.2058 + 1.2059 + TypeValue.encode(data, value.media); 1.2060 + }, 1.2061 + 1.2062 + /** 1.2063 + * @param data 1.2064 + * A wrapped object to store encoded raw data. 1.2065 + * @param value 1.2066 + * An object containing `media` and `params` properties. 1.2067 + */ 1.2068 + encodeMediaType: function(data, value) { 1.2069 + let entry = WSP_WELL_KNOWN_CONTENT_TYPES[value.media.toLowerCase()]; 1.2070 + if (entry) { 1.2071 + IntegerValue.encode(data, entry.number); 1.2072 + } else { 1.2073 + NullTerminatedTexts.encode(data, value.media); 1.2074 + } 1.2075 + 1.2076 + Parameter.encodeMultiple(data, value.params); 1.2077 + }, 1.2078 + 1.2079 + /** 1.2080 + * @param data 1.2081 + * A wrapped object to store encoded raw data. 1.2082 + * @param value 1.2083 + * An object containing `media` and `params` properties. 1.2084 + */ 1.2085 + encodeContentGeneralForm: function(data, value) { 1.2086 + let begin = data.offset; 1.2087 + this.encodeMediaType(data, value); 1.2088 + 1.2089 + // Calculate how much octets will be written and seek back. 1.2090 + // TODO: use memmove, see bug 730873 1.2091 + let len = data.offset - begin; 1.2092 + data.offset = begin; 1.2093 + 1.2094 + ValueLength.encode(data, len); 1.2095 + this.encodeMediaType(data, value); 1.2096 + }, 1.2097 + 1.2098 + /** 1.2099 + * @param data 1.2100 + * A wrapped object to store encoded raw data. 1.2101 + * @param value 1.2102 + * An object containing `media` and `params` properties. 1.2103 + */ 1.2104 + encode: function(data, value) { 1.2105 + let begin = data.offset; 1.2106 + 1.2107 + try { 1.2108 + this.encodeConstrainedMedia(data, value); 1.2109 + } catch (e) { 1.2110 + data.offset = begin; 1.2111 + this.encodeContentGeneralForm(data, value); 1.2112 + } 1.2113 + }, 1.2114 +}; 1.2115 + 1.2116 +/** 1.2117 + * Application-id-value = Uri-value | App-assigned-code 1.2118 + * App-assigned-code = Integer-value 1.2119 + * 1.2120 + * @see WAP-230-WSP-20010705-a clause 8.4.2.54 1.2121 + */ 1.2122 +this.ApplicationIdValue = { 1.2123 + /** 1.2124 + * @param data 1.2125 + * A wrapped object containing raw PDU data. 1.2126 + * 1.2127 + * @return Decoded string value. 1.2128 + * 1.2129 + * @throws CodeError if decoded application id number is an array. 1.2130 + * @throws NotWellKnownEncodingError if decoded well-known application id 1.2131 + * number is not registered or supported. 1.2132 + */ 1.2133 + decode: function(data) { 1.2134 + let begin = data.offset; 1.2135 + try { 1.2136 + return UriValue.decode(data); 1.2137 + } catch (e) {} 1.2138 + 1.2139 + data.offset = begin; 1.2140 + 1.2141 + // `decodeIntegerValue` can return a array, which doesn't apply here. 1.2142 + let numOrArray = IntegerValue.decode(data); 1.2143 + if (typeof numOrArray != "number") { 1.2144 + throw new CodeError("Application-id-value: invalid integer type"); 1.2145 + } 1.2146 + 1.2147 + let id = numOrArray; 1.2148 + let entry = OMNA_PUSH_APPLICATION_IDS[id]; 1.2149 + if (!entry) { 1.2150 + throw new NotWellKnownEncodingError( 1.2151 + "Application-id-value: not well known id: " + id); 1.2152 + } 1.2153 + 1.2154 + return entry.urn; 1.2155 + }, 1.2156 +}; 1.2157 + 1.2158 +this.PduHelper = { 1.2159 + /** 1.2160 + * @param data 1.2161 + * A UInt8Array of data for decode. 1.2162 + * @param charset 1.2163 + * charset for decode 1.2164 + * 1.2165 + * @return Decoded string. 1.2166 + */ 1.2167 + decodeStringContent: function(data, charset) { 1.2168 + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.2169 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.2170 + 1.2171 + let entry; 1.2172 + if (charset) { 1.2173 + entry = WSP_WELL_KNOWN_CHARSETS[charset]; 1.2174 + } 1.2175 + // Set converter to default one if (entry && entry.converter) is null. 1.2176 + // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 1.2177 + conv.charset = (entry && entry.converter) || "UTF-8"; 1.2178 + try { 1.2179 + return conv.convertFromByteArray(data, data.length); 1.2180 + } catch (e) { 1.2181 + } 1.2182 + return null; 1.2183 + }, 1.2184 + 1.2185 + /** 1.2186 + * @param strContent 1.2187 + * Decoded string content. 1.2188 + * @param charset 1.2189 + * Charset for encode. 1.2190 + * 1.2191 + * @return An encoded UInt8Array of string content. 1.2192 + */ 1.2193 + encodeStringContent: function(strContent, charset) { 1.2194 + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.2195 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.2196 + 1.2197 + let entry; 1.2198 + if (charset) { 1.2199 + entry = WSP_WELL_KNOWN_CHARSETS[charset]; 1.2200 + } 1.2201 + // Set converter to default one if (entry && entry.converter) is null. 1.2202 + // @see OMA-TS-MMS-CONF-V1_3-20050526-D 7.1.9 1.2203 + conv.charset = (entry && entry.converter) || "UTF-8"; 1.2204 + try { 1.2205 + return conv.convertToByteArray(strContent); 1.2206 + } catch (e) { 1.2207 + } 1.2208 + return null; 1.2209 + }, 1.2210 + 1.2211 + /** 1.2212 + * Parse multiple header fields with end mark. 1.2213 + * 1.2214 + * @param data 1.2215 + * A wrapped object containing raw PDU data. 1.2216 + * @param end 1.2217 + * An ending offset indicating the end of headers. 1.2218 + * @param headers [optional] 1.2219 + * An optional object to store parsed header fields. Created 1.2220 + * automatically if undefined. 1.2221 + * 1.2222 + * @return A object containing decoded header fields as its attributes. 1.2223 + */ 1.2224 + parseHeaders: function(data, end, headers) { 1.2225 + if (!headers) { 1.2226 + headers = {}; 1.2227 + } 1.2228 + 1.2229 + let header; 1.2230 + while (data.offset < end) { 1.2231 + try { 1.2232 + header = Header.decode(data); 1.2233 + } catch (e) { 1.2234 + break; 1.2235 + } 1.2236 + if (header) { 1.2237 + headers[header.name] = header.value; 1.2238 + } 1.2239 + } 1.2240 + 1.2241 + if (data.offset != end) { 1.2242 + debug("Parser expects ending in " + end + ", but in " + data.offset); 1.2243 + // Explicitly seek to end in case of skipped header fields. 1.2244 + data.offset = end; 1.2245 + } 1.2246 + 1.2247 + return headers; 1.2248 + }, 1.2249 + 1.2250 + /** 1.2251 + * @param data 1.2252 + * A wrapped object containing raw PDU data. 1.2253 + * @param msg 1.2254 + * Message object to be populated with decoded header fields. 1.2255 + * 1.2256 + * @see WAP-230-WSP-20010705-a clause 8.2.4 1.2257 + */ 1.2258 + parsePushHeaders: function(data, msg) { 1.2259 + if (!msg.headers) { 1.2260 + msg.headers = {}; 1.2261 + } 1.2262 + 1.2263 + let headersLen = UintVar.decode(data); 1.2264 + let headersEnd = data.offset + headersLen; 1.2265 + 1.2266 + let contentType = ContentTypeValue.decode(data); 1.2267 + msg.headers["content-type"] = contentType; 1.2268 + 1.2269 + msg.headers = this.parseHeaders(data, headersEnd, msg.headers); 1.2270 + }, 1.2271 + 1.2272 + /** 1.2273 + * @param data 1.2274 + * A wrapped object containing raw PDU data. 1.2275 + * 1.2276 + * @return An array of objects representing multipart entries or null in case 1.2277 + * of errors found. 1.2278 + * 1.2279 + * @see WAP-230-WSP-20010705-a section 8.5 1.2280 + */ 1.2281 + parseMultiPart: function(data) { 1.2282 + let nEntries = UintVar.decode(data); 1.2283 + if (!nEntries) { 1.2284 + return null; 1.2285 + } 1.2286 + 1.2287 + let parts = new Array(nEntries); 1.2288 + for (let i = 0; i < nEntries; i++) { 1.2289 + // Length of the ContentType and Headers fields combined. 1.2290 + let headersLen = UintVar.decode(data); 1.2291 + // Length of the Data field 1.2292 + let contentLen = UintVar.decode(data); 1.2293 + 1.2294 + let headersEnd = data.offset + headersLen; 1.2295 + let contentEnd = headersEnd + contentLen; 1.2296 + 1.2297 + try { 1.2298 + let headers = {}; 1.2299 + 1.2300 + let contentType = ContentTypeValue.decode(data); 1.2301 + headers["content-type"] = contentType; 1.2302 + headers["content-length"] = contentLen; 1.2303 + 1.2304 + headers = this.parseHeaders(data, headersEnd, headers); 1.2305 + 1.2306 + let octetArray = Octet.decodeMultiple(data, contentEnd); 1.2307 + let content = null; 1.2308 + let charset = headers["content-type"].params && 1.2309 + headers["content-type"].params.charset 1.2310 + ? headers["content-type"].params.charset.charset 1.2311 + : null; 1.2312 + 1.2313 + let mimeType = headers["content-type"].media; 1.2314 + 1.2315 + if (mimeType) { 1.2316 + if (mimeType == "application/smil") { 1.2317 + // If the content is a SMIL type, convert it to a string. 1.2318 + // We hope to save and expose the SMIL content in a string way. 1.2319 + content = this.decodeStringContent(octetArray, charset); 1.2320 + } else if (mimeType.indexOf("text/") == 0 && charset != "utf-8") { 1.2321 + // If the content is a "text/plain" type, we have to make sure 1.2322 + // the encoding of the blob content should always be "utf-8". 1.2323 + let tmpStr = this.decodeStringContent(octetArray, charset); 1.2324 + let encoder = new TextEncoder("UTF-8"); 1.2325 + content = new Blob([encoder.encode(tmpStr)], {type : mimeType}); 1.2326 + 1.2327 + // Make up the missing encoding info. 1.2328 + if (!headers["content-type"].params) { 1.2329 + headers["content-type"].params = {}; 1.2330 + } 1.2331 + if (!headers["content-type"].params.charset) { 1.2332 + headers["content-type"].params.charset = {}; 1.2333 + } 1.2334 + headers["content-type"].params.charset.charset = "utf-8"; 1.2335 + } 1.2336 + } 1.2337 + 1.2338 + if (!content) { 1.2339 + content = new Blob([octetArray], {type : mimeType}); 1.2340 + } 1.2341 + 1.2342 + parts[i] = { 1.2343 + index: i, 1.2344 + headers: headers, 1.2345 + content: content, 1.2346 + }; 1.2347 + } catch (e) { 1.2348 + debug("Failed to parse multipart entry, message: " + e.message); 1.2349 + // Placeholder to keep original index of following entries. 1.2350 + parts[i] = null; 1.2351 + } 1.2352 + 1.2353 + if (data.offset != contentEnd) { 1.2354 + // Seek to entry boundary for next entry. 1.2355 + data.offset = contentEnd; 1.2356 + } 1.2357 + } 1.2358 + 1.2359 + return parts; 1.2360 + }, 1.2361 + 1.2362 + /** 1.2363 + * @param data 1.2364 + * A wrapped object containing raw PDU data. 1.2365 + * @param isSessionless 1.2366 + * Whether or not the PDU contains a session less WSP PDU. 1.2367 + * @param msg [optional] 1.2368 + * Optional pre-defined PDU object. 1.2369 + * 1.2370 + * @return Parsed WSP PDU object or null in case of errors found. 1.2371 + */ 1.2372 + parse: function(data, isSessionless, msg) { 1.2373 + if (!msg) { 1.2374 + msg = { 1.2375 + type: null, 1.2376 + }; 1.2377 + } 1.2378 + 1.2379 + try { 1.2380 + if (isSessionless) { 1.2381 + // "The `transactionId` is used to associate requests with replies in 1.2382 + // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1 1.2383 + msg.transactionId = Octet.decode(data); 1.2384 + } 1.2385 + 1.2386 + msg.type = Octet.decode(data); 1.2387 + switch (msg.type) { 1.2388 + case WSP_PDU_TYPE_PUSH: 1.2389 + this.parsePushHeaders(data, msg); 1.2390 + break; 1.2391 + } 1.2392 + } catch (e) { 1.2393 + debug("Parse error. Message: " + e.message); 1.2394 + msg = null; 1.2395 + } 1.2396 + 1.2397 + return msg; 1.2398 + }, 1.2399 + 1.2400 + /** 1.2401 + * @param multiStream 1.2402 + * An exsiting nsIMultiplexInputStream. 1.2403 + * @param array 1.2404 + * An octet array. 1.2405 + * @param length 1.2406 + * Max number of octets to be coverted into an input stream. 1.2407 + */ 1.2408 + appendArrayToMultiStream: function(multiStream, array, length) { 1.2409 + let storageStream = Cc["@mozilla.org/storagestream;1"] 1.2410 + .createInstance(Ci.nsIStorageStream); 1.2411 + storageStream.init(4096, length, null); 1.2412 + 1.2413 + let boStream = Cc["@mozilla.org/binaryoutputstream;1"] 1.2414 + .createInstance(Ci.nsIBinaryOutputStream); 1.2415 + boStream.setOutputStream(storageStream.getOutputStream(0)); 1.2416 + boStream.writeByteArray(array, length); 1.2417 + boStream.close(); 1.2418 + 1.2419 + multiStream.appendStream(storageStream.newInputStream(0)); 1.2420 + }, 1.2421 + 1.2422 + /** 1.2423 + * @param multiStream 1.2424 + * An exsiting nsIMultiplexInputStream. 1.2425 + * @param parts 1.2426 + * An array of objects representing multipart entries. 1.2427 + * 1.2428 + * @see WAP-230-WSP-20010705-a section 8.5 1.2429 + */ 1.2430 + composeMultiPart: function(multiStream, parts) { 1.2431 + // Encode multipart header 1.2432 + { 1.2433 + let data = {array: [], offset: 0}; 1.2434 + UintVar.encode(data, parts.length); 1.2435 + debug("Encoded multipart header: " + JSON.stringify(data.array)); 1.2436 + this.appendArrayToMultiStream(multiStream, data.array, data.offset); 1.2437 + } 1.2438 + 1.2439 + // Encode each part 1.2440 + for (let i = 0; i < parts.length; i++) { 1.2441 + let part = parts[i]; 1.2442 + let data = {array: [], offset: 0}; 1.2443 + 1.2444 + // Encode Content-Type 1.2445 + let contentType = part.headers["content-type"]; 1.2446 + ContentTypeValue.encode(data, contentType); 1.2447 + 1.2448 + // Encode other headers 1.2449 + if (Object.keys(part).length > 1) { 1.2450 + // Remove Content-Type temporarily 1.2451 + delete part.headers["content-type"]; 1.2452 + 1.2453 + for (let name in part.headers) { 1.2454 + Header.encode(data, {name: name, value: part.headers[name]}); 1.2455 + } 1.2456 + 1.2457 + // Restore Content-Type back 1.2458 + part.headers["content-type"] = contentType; 1.2459 + } 1.2460 + 1.2461 + // Encode headersLen, DataLen 1.2462 + let headersLen = data.offset; 1.2463 + let content = part.content; 1.2464 + UintVar.encode(data, headersLen); 1.2465 + if (typeof content === "string") { 1.2466 + let charset; 1.2467 + if (contentType && contentType.params && contentType.params.charset && 1.2468 + contentType.params.charset.charset) { 1.2469 + charset = contentType.params.charset.charset; 1.2470 + } 1.2471 + content = this.encodeStringContent(content, charset); 1.2472 + UintVar.encode(data, content.length); 1.2473 + } else if (part.content instanceof Uint8Array) { 1.2474 + UintVar.encode(data, content.length); 1.2475 + } else { 1.2476 + throw new TypeError(); 1.2477 + } 1.2478 + 1.2479 + // Move them to the beginning of encoded octet array. 1.2480 + let slice1 = data.array.slice(headersLen); 1.2481 + let slice2 = data.array.slice(0, headersLen); 1.2482 + data.array = slice1.concat(slice2); 1.2483 + debug("Encoded per-part header: " + JSON.stringify(data.array)); 1.2484 + 1.2485 + // Append per-part header 1.2486 + this.appendArrayToMultiStream(multiStream, data.array, data.offset); 1.2487 + // Append part content 1.2488 + this.appendArrayToMultiStream(multiStream, content, content.length); 1.2489 + } 1.2490 + }, 1.2491 +}; 1.2492 + 1.2493 +// WSP Header Field Name Assignments 1.2494 +// Note: Items commented out are either deprecated or not implemented. 1.2495 +// Deprecated items should only be supported for backward compatibility 1.2496 +// purpose. 1.2497 +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. 1.2498 +this.WSP_HEADER_FIELDS = (function() { 1.2499 + let names = {}; 1.2500 + function add(name, number, coder) { 1.2501 + let entry = { 1.2502 + name: name, 1.2503 + number: number, 1.2504 + coder: coder, 1.2505 + }; 1.2506 + names[name] = names[number] = entry; 1.2507 + } 1.2508 + 1.2509 + // Encoding Version: 1.1 1.2510 + //add("accept", 0x00); 1.2511 + //add("accept-charset", 0x01); Deprecated 1.2512 + //add("accept-encoding", 0x02); Deprecated 1.2513 + //add("accept-language", 0x03); 1.2514 + //add("accept-ranges", 0x04); 1.2515 + add("age", 0x05, DeltaSecondsValue); 1.2516 + //add("allow", 0x06); 1.2517 + //add("authorization", 0x07); 1.2518 + //add("cache-control", 0x08); Deprecated 1.2519 + //add("connection", 0x09); 1.2520 + //add("content-base", 0x0A); Deprecated 1.2521 + //add("content-encoding", 0x0B); 1.2522 + //add("content-language", 0x0C); 1.2523 + add("content-length", 0x0D, IntegerValue); 1.2524 + add("content-location", 0x0E, UriValue); 1.2525 + //add("content-md5", 0x0F); 1.2526 + //add("content-range", 0x10); Deprecated 1.2527 + add("content-type", 0x11, ContentTypeValue); 1.2528 + add("date", 0x12, DateValue); 1.2529 + add("etag", 0x13, TextString); 1.2530 + add("expires", 0x14, DateValue); 1.2531 + add("from", 0x15, TextString); 1.2532 + add("host", 0x16, TextString); 1.2533 + add("if-modified-since", 0x17, DateValue); 1.2534 + add("if-match", 0x18, TextString); 1.2535 + add("if-none-match", 0x19, TextString); 1.2536 + //add("if-range", 0x1A); 1.2537 + add("if-unmodified-since", 0x1B, DateValue); 1.2538 + add("location", 0x1C, UriValue); 1.2539 + add("last-modified", 0x1D, DateValue); 1.2540 + add("max-forwards", 0x1E, IntegerValue); 1.2541 + //add("pragma", 0x1F); 1.2542 + //add("proxy-authenticate", 0x20); 1.2543 + //add("proxy-authentication", 0x21); 1.2544 + //add("public", 0x22); 1.2545 + //add("range", 0x23); 1.2546 + add("referer", 0x24, UriValue); 1.2547 + //add("retry-after", 0x25); 1.2548 + add("server", 0x26, TextString); 1.2549 + //add("transfer-encoding", 0x27); 1.2550 + add("upgrade", 0x28, TextString); 1.2551 + add("user-agent", 0x29, TextString); 1.2552 + //add("vary", 0x2A); 1.2553 + add("via", 0x2B, TextString); 1.2554 + //add("warning", 0x2C); 1.2555 + //add("www-authenticate", 0x2D); 1.2556 + //add("content-disposition", 0x2E); Deprecated 1.2557 + 1.2558 + // Encoding Version: 1.2 1.2559 + add("x-wap-application-id", 0x2F, ApplicationIdValue); 1.2560 + add("x-wap-content-uri", 0x30, UriValue); 1.2561 + add("x-wap-initiator-uri", 0x31, UriValue); 1.2562 + //add("accept-application", 0x32); 1.2563 + add("bearer-indication", 0x33, IntegerValue); 1.2564 + add("push-flag", 0x34, ShortInteger); 1.2565 + add("profile", 0x35, UriValue); 1.2566 + //add("profile-diff", 0x36); 1.2567 + //add("profile-warning", 0x37); Deprecated 1.2568 + 1.2569 + // Encoding Version: 1.3 1.2570 + //add("expect", 0x38); 1.2571 + //add("te", 0x39); 1.2572 + //add("trailer", 0x3A); 1.2573 + add("accept-charset", 0x3B, AcceptCharsetValue); 1.2574 + //add("accept-encoding", 0x3C); 1.2575 + //add("cache-control", 0x3D); Deprecated 1.2576 + //add("content-range", 0x3E); 1.2577 + add("x-wap-tod", 0x3F, DateValue); 1.2578 + add("content-id", 0x40, QuotedString); 1.2579 + //add("set-cookie", 0x41); 1.2580 + //add("cookie", 0x42); 1.2581 + //add("encoding-version", 0x43); 1.2582 + 1.2583 + // Encoding Version: 1.4 1.2584 + //add("profile-warning", 0x44); 1.2585 + //add("content-disposition", 0x45); 1.2586 + //add("x-wap-security", 0x46); 1.2587 + //add("cache-control", 0x47); 1.2588 + 1.2589 + return names; 1.2590 +})(); 1.2591 + 1.2592 +// WSP Content Type Assignments 1.2593 +// @see http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.aspx 1.2594 +this.WSP_WELL_KNOWN_CONTENT_TYPES = (function() { 1.2595 + let types = {}; 1.2596 + 1.2597 + function add(type, number) { 1.2598 + let entry = { 1.2599 + type: type, 1.2600 + number: number, 1.2601 + }; 1.2602 + // For case like "text/x-vCalendar", we need toLoweCase() for generating 1.2603 + // the same index. 1.2604 + types[type.toLowerCase()] = types[number] = entry; 1.2605 + } 1.2606 + 1.2607 + // Well Known Values 1.2608 + // Encoding Version: 1.1 1.2609 + add("*/*", 0x00); 1.2610 + add("text/*", 0x01); 1.2611 + add("text/html", 0x02); 1.2612 + add("text/plain", 0x03); 1.2613 + add("text/x-hdml", 0x04); 1.2614 + add("text/x-ttml", 0x05); 1.2615 + add("text/x-vCalendar", 0x06); 1.2616 + add("text/x-vCard", 0x07); 1.2617 + add("text/vnd.wap.wml", 0x08); 1.2618 + add("text/vnd.wap.wmlscript", 0x09); 1.2619 + add("text/vnd.wap.wta-event", 0x0A); 1.2620 + add("multipart/*", 0x0B); 1.2621 + add("multipart/mixed", 0x0C); 1.2622 + add("multipart/form-data", 0x0D); 1.2623 + add("multipart/byterantes", 0x0E); 1.2624 + add("multipart/alternative", 0x0F); 1.2625 + add("application/*", 0x10); 1.2626 + add("application/java-vm", 0x11); 1.2627 + add("application/x-www-form-urlencoded", 0x12); 1.2628 + add("application/x-hdmlc", 0x13); 1.2629 + add("application/vnd.wap.wmlc", 0x14); 1.2630 + add("application/vnd.wap.wmlscriptc", 0x15); 1.2631 + add("application/vnd.wap.wta-eventc", 0x16); 1.2632 + add("application/vnd.wap.uaprof", 0x17); 1.2633 + add("application/vnd.wap.wtls-ca-certificate", 0x18); 1.2634 + add("application/vnd.wap.wtls-user-certificate", 0x19); 1.2635 + add("application/x-x509-ca-cert", 0x1A); 1.2636 + add("application/x-x509-user-cert", 0x1B); 1.2637 + add("image/*", 0x1C); 1.2638 + add("image/gif", 0x1D); 1.2639 + add("image/jpeg", 0x1E); 1.2640 + add("image/tiff", 0x1F); 1.2641 + add("image/png", 0x20); 1.2642 + add("image/vnd.wap.wbmp", 0x21); 1.2643 + add("application/vnd.wap.multipart.*", 0x22); 1.2644 + add("application/vnd.wap.multipart.mixed", 0x23); 1.2645 + add("application/vnd.wap.multipart.form-data", 0x24); 1.2646 + add("application/vnd.wap.multipart.byteranges", 0x25); 1.2647 + add("application/vnd.wap.multipart.alternative", 0x26); 1.2648 + add("application/xml", 0x27); 1.2649 + add("text/xml", 0x28); 1.2650 + add("application/vnd.wap.wbxml", 0x29); 1.2651 + add("application/x-x968-cross-cert", 0x2A); 1.2652 + add("application/x-x968-ca-cert", 0x2B); 1.2653 + add("application/x-x968-user-cert", 0x2C); 1.2654 + add("text/vnd.wap.si", 0x2D); 1.2655 + 1.2656 + // Encoding Version: 1.2 1.2657 + add("application/vnd.wap.sic", 0x2E); 1.2658 + add("text/vnd.wap.sl", 0x2F); 1.2659 + add("application/vnd.wap.slc", 0x30); 1.2660 + add("text/vnd.wap.co", 0x31); 1.2661 + add("application/vnd.wap.coc", 0x32); 1.2662 + add("application/vnd.wap.multipart.related", 0x33); 1.2663 + add("application/vnd.wap.sia", 0x34); 1.2664 + 1.2665 + // Encoding Version: 1.3 1.2666 + add("text/vnd.wap.connectivity-xml", 0x35); 1.2667 + add("application/vnd.wap.connectivity-wbxml", 0x36); 1.2668 + 1.2669 + // Encoding Version: 1.4 1.2670 + add("application/pkcs7-mime", 0x37); 1.2671 + add("application/vnd.wap.hashed-certificate", 0x38); 1.2672 + add("application/vnd.wap.signed-certificate", 0x39); 1.2673 + add("application/vnd.wap.cert-response", 0x3A); 1.2674 + add("application/xhtml+xml", 0x3B); 1.2675 + add("application/wml+xml", 0x3C); 1.2676 + add("text/css", 0x3D); 1.2677 + add("application/vnd.wap.mms-message", 0x3E); 1.2678 + add("application/vnd.wap.rollover-certificate", 0x3F); 1.2679 + 1.2680 + // Encoding Version: 1.5 1.2681 + add("application/vnd.wap.locc+wbxml", 0x40); 1.2682 + add("application/vnd.wap.loc+xml", 0x41); 1.2683 + add("application/vnd.syncml.dm+wbxml", 0x42); 1.2684 + add("application/vnd.syncml.dm+xml", 0x43); 1.2685 + add("application/vnd.syncml.notification", 0x44); 1.2686 + add("application/vnd.wap.xhtml+xml", 0x45); 1.2687 + add("application/vnd.wv.csp.cir", 0x46); 1.2688 + add("application/vnd.oma.dd+xml", 0x47); 1.2689 + add("application/vnd.oma.drm.message", 0x48); 1.2690 + add("application/vnd.oma.drm.content", 0x49); 1.2691 + add("application/vnd.oma.drm.rights+xml", 0x4A); 1.2692 + add("application/vnd.oma.drm.rights+wbxml", 0x4B); 1.2693 + add("application/vnd.wv.csp+xml", 0x4C); 1.2694 + add("application/vnd.wv.csp+wbxml", 0x4D); 1.2695 + add("application/vnd.syncml.ds.notification", 0x4E); 1.2696 + 1.2697 + // Encoding Version: 1.6 1.2698 + add("audio/*", 0x4F); 1.2699 + add("video/*", 0x50); 1.2700 + 1.2701 + // Encoding Version: TBD 1.2702 + add("application/vnd.oma.dd2+xml", 0x51); 1.2703 + add("application/mikey", 0x52); 1.2704 + add("application/vnd.oma.dcd", 0x53); 1.2705 + add("application/vnd.oma.dcdc", 0x54); 1.2706 + add("text/x-vMessage", 0x55); 1.2707 + add("application/vnd.omads-email+wbxml", 0x56); 1.2708 + add("text/x-vBookmark", 0x57); 1.2709 + add("application/vnd.syncml.dm.notification", 0x58); 1.2710 + add("application/octet-stream", 0x5A); 1.2711 + 1.2712 + return types; 1.2713 +})(); 1.2714 + 1.2715 +// WSP Well-Known Parameter Assignments 1.2716 +// Note: Items commented out are either deprecated or not implemented. 1.2717 +// Deprecated items should not be used. 1.2718 +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. 1.2719 +this.WSP_WELL_KNOWN_PARAMS = (function() { 1.2720 + let params = {}; 1.2721 + 1.2722 + function add(name, number, coder) { 1.2723 + let entry = { 1.2724 + name: name, 1.2725 + number: number, 1.2726 + coder: coder, 1.2727 + }; 1.2728 + params[name] = params[number] = entry; 1.2729 + } 1.2730 + 1.2731 + // Encoding Version: 1.1 1.2732 + add("q", 0x00, QValue); 1.2733 + add("charset", 0x01, WellKnownCharset); 1.2734 + add("level", 0x02, VersionValue); 1.2735 + add("type", 0x03, IntegerValue); 1.2736 + add("name", 0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet. 1.2737 + //add("filename", 0x06); Deprecated 1.2738 + add("differences", 0x07, FieldName); 1.2739 + add("padding", 0x08, ShortInteger); 1.2740 + 1.2741 + // Encoding Version: 1.2 1.2742 + add("type", 0x09, TypeValue); 1.2743 + add("start", 0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile. 1.2744 + //add("start-info", 0x0B); Deprecated 1.2745 + 1.2746 + // Encoding Version: 1.3 1.2747 + //add("comment", 0x0C); Deprecated 1.2748 + //add("domain", 0x0D); Deprecated 1.2749 + add("max-age", 0x0E, DeltaSecondsValue); 1.2750 + //add("path", 0x0F); Deprecated 1.2751 + add("secure", 0x10, NoValue); 1.2752 + 1.2753 + // Encoding Version: 1.4 1.2754 + add("sec", 0x11, ShortInteger); 1.2755 + add("mac", 0x12, TextValue); 1.2756 + add("creation-date", 0x13, DateValue); 1.2757 + add("modification-date", 0x14, DateValue); 1.2758 + add("read-date", 0x15, DateValue); 1.2759 + add("size", 0x16, IntegerValue); 1.2760 + //add("name", 0x17, TextValue); // Not supported in some carriers, eg. Hinet. 1.2761 + add("filename", 0x18, TextValue); 1.2762 + //add("start", 0x19, TextValue); // Not supported in some carriers, eg. Hinet. 1.2763 + add("start-info", 0x1A, TextValue); 1.2764 + add("comment", 0x1B, TextValue); 1.2765 + add("domain", 0x1C, TextValue); 1.2766 + add("path", 0x1D, TextValue); 1.2767 + 1.2768 + return params; 1.2769 +})(); 1.2770 + 1.2771 +// WSP Character Set Assignments 1.2772 +// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers. 1.2773 +// @see http://www.iana.org/assignments/character-sets 1.2774 +this.WSP_WELL_KNOWN_CHARSETS = (function() { 1.2775 + let charsets = {}; 1.2776 + 1.2777 + function add(name, number, converter) { 1.2778 + let entry = { 1.2779 + name: name, 1.2780 + number: number, 1.2781 + converter: converter, 1.2782 + }; 1.2783 + 1.2784 + charsets[name] = charsets[number] = entry; 1.2785 + } 1.2786 + 1.2787 + add("us-ascii", 3, null); 1.2788 + add("iso-8859-1", 4, "ISO-8859-1"); 1.2789 + add("iso-8859-2", 5, "ISO-8859-2"); 1.2790 + add("iso-8859-3", 6, "ISO-8859-3"); 1.2791 + add("iso-8859-4", 7, "ISO-8859-4"); 1.2792 + add("iso-8859-5", 8, "ISO-8859-5"); 1.2793 + add("iso-8859-6", 9, "ISO-8859-6"); 1.2794 + add("iso-8859-7", 10, "ISO-8859-7"); 1.2795 + add("iso-8859-8", 11, "ISO-8859-8"); 1.2796 + add("iso-8859-9", 12, "ISO-8859-9"); 1.2797 + add("iso-8859-10", 13, "ISO-8859-10"); 1.2798 + add("shift_jis", 17, "Shift_JIS"); 1.2799 + add("euc-jp", 18, "EUC-JP"); 1.2800 + add("iso-2022-kr", 37, "ISO-2022-KR"); 1.2801 + add("euc-kr", 38, "EUC-KR"); 1.2802 + add("iso-2022-jp", 39, "ISO-2022-JP"); 1.2803 + add("iso-2022-jp-2", 40, "iso-2022-jp-2"); 1.2804 + add("iso-8859-6-e", 81, "ISO-8859-6-E"); 1.2805 + add("iso-8859-6-i", 82, "ISO-8859-6-I"); 1.2806 + add("iso-8859-8-e", 84, "ISO-8859-8-E"); 1.2807 + add("iso-8859-8-i", 85, "ISO-8859-8-I"); 1.2808 + add("utf-8", 106, "UTF-8"); 1.2809 + add("iso-10646-ucs-2", 1000, "iso-10646-ucs-2"); 1.2810 + add("utf-16", 1015, "UTF-16"); 1.2811 + add("gb2312", 2025, "GB2312"); 1.2812 + add("big5", 2026, "Big5"); 1.2813 + add("koi8-r", 2084, "KOI8-R"); 1.2814 + add("windows-1252", 2252, "windows-1252"); 1.2815 + 1.2816 + return charsets; 1.2817 +})(); 1.2818 + 1.2819 +// OMNA PUSH Application ID 1.2820 +// @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx 1.2821 +this.OMNA_PUSH_APPLICATION_IDS = (function() { 1.2822 + let ids = {}; 1.2823 + 1.2824 + function add(urn, number) { 1.2825 + let entry = { 1.2826 + urn: urn, 1.2827 + number: number, 1.2828 + }; 1.2829 + 1.2830 + ids[urn] = ids[number] = entry; 1.2831 + } 1.2832 + 1.2833 + add("x-wap-application:wml.ua", 0x02); 1.2834 + add("x-wap-application:mms.ua", 0x04); 1.2835 + 1.2836 + return ids; 1.2837 +})(); 1.2838 + 1.2839 +let debug; 1.2840 +if (DEBUG) { 1.2841 + debug = function(s) { 1.2842 + dump("-@- WspPduHelper: " + s + "\n"); 1.2843 + }; 1.2844 +} else { 1.2845 + debug = function(s) {}; 1.2846 +} 1.2847 + 1.2848 +this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([ 1.2849 + // Constant values 1.2850 + "WSP_HEADER_FIELDS", 1.2851 + "WSP_WELL_KNOWN_CONTENT_TYPES", 1.2852 + "WSP_WELL_KNOWN_PARAMS", 1.2853 + "WSP_WELL_KNOWN_CHARSETS", 1.2854 + "OMNA_PUSH_APPLICATION_IDS", 1.2855 + 1.2856 + // Error classes 1.2857 + "CodeError", 1.2858 + "FatalCodeError", 1.2859 + "NotWellKnownEncodingError", 1.2860 + 1.2861 + // Utility functions 1.2862 + "ensureHeader", 1.2863 + "skipValue", 1.2864 + "decodeAlternatives", 1.2865 + "encodeAlternatives", 1.2866 + 1.2867 + // Decoders 1.2868 + "Octet", 1.2869 + "Text", 1.2870 + "NullTerminatedTexts", 1.2871 + "Token", 1.2872 + "URIC", 1.2873 + "TextString", 1.2874 + "TokenText", 1.2875 + "QuotedString", 1.2876 + "ShortInteger", 1.2877 + "LongInteger", 1.2878 + "UintVar", 1.2879 + "ConstrainedEncoding", 1.2880 + "ValueLength", 1.2881 + "NoValue", 1.2882 + "TextValue", 1.2883 + "IntegerValue", 1.2884 + "DateValue", 1.2885 + "DeltaSecondsValue", 1.2886 + "QValue", 1.2887 + "VersionValue", 1.2888 + "UriValue", 1.2889 + "TypeValue", 1.2890 + "Parameter", 1.2891 + "Header", 1.2892 + "WellKnownHeader", 1.2893 + "ApplicationHeader", 1.2894 + "FieldName", 1.2895 + "AcceptCharsetValue", 1.2896 + "WellKnownCharset", 1.2897 + "ContentTypeValue", 1.2898 + "ApplicationIdValue", 1.2899 + 1.2900 + // Parser 1.2901 + "PduHelper", 1.2902 +]);