1/* Copyright 2016 Software Freedom Conservancy Inc. 2 * 3 * This software is licensed under the GNU Lesser General Public License 4 * (version 2.1 or later). See the COPYING file in this distribution. 5 */ 6 7/** 8 * The representation of an IMAP parenthesized list. 9 * 10 * See [[http://tools.ietf.org/html/rfc3501#section-4.4]] 11 */ 12 13public class Geary.Imap.ListParameter : Geary.Imap.Parameter { 14 /** 15 * The maximum length a literal parameter may be to be auto-converted to a StringParameter 16 * in the StringParameter getters. 17 */ 18 public const int MAX_STRING_LITERAL_LENGTH = 4096; 19 20 /** 21 * Returns the number of {@link Parameter}s held in this {@link ListParameter}. 22 */ 23 public int size { 24 get { 25 return list.size; 26 } 27 } 28 29 private Gee.List<Parameter> list = new Gee.ArrayList<Parameter>(); 30 31 32 /** Constructs a new, empty list. */ 33 public ListParameter() { 34 // noop 35 } 36 37 /** Constructs a new list with a single parameter. */ 38 public ListParameter.single(Parameter param) { 39 base(); 40 add(param); 41 } 42 43 /** 44 * Appends a parameter to the end of this list. 45 * 46 * The same {@link Parameter} can't be added more than once to the 47 * same {@link ListParameter}. There are no other restrictions, 48 * however. 49 * 50 * @return true if added. 51 */ 52 public bool add(Parameter param) { 53 return this.list.add(param); 54 } 55 56 /** 57 * Adds all parameters in the given collection to this list. 58 * 59 * The same {@link Parameter} can't be added more than once to the 60 * same {@link ListParameter}. There are no other restrictions, 61 * however. 62 * 63 * @return number of Parameters added. 64 */ 65 public int add_all(Gee.Collection<Parameter> params) { 66 int count = 0; 67 foreach (Parameter param in params) { 68 count += add(param) ? 1 : 0; 69 } 70 return count; 71 } 72 73 /** 74 * Adds all elements in the given list to the end of this list. 75 * 76 * The difference between this call and {@link add} is that add() will simply insert the 77 * {@link Parameter} to the tail of the list. Thus, add(ListParameter) will add a child list 78 * inside this list, i.e. add(ListParameter("three")): 79 * 80 * (one two (three)) 81 * 82 * Instead, extend(ListParameter("three")) adds each element of the ListParameter to this one, not 83 * creating a child: 84 * 85 * (one two three) 86 * 87 * @return Number of added elements. This will not abort if an 88 * element fails to be added. 89 */ 90 public int extend(ListParameter listp) { 91 return add_all(listp.list); 92 } 93 94 /** 95 * Clears the {@link ListParameter} of all its children. 96 */ 97 public void clear() { 98 list.clear(); 99 } 100 101 // 102 // Parameter retrieval 103 // 104 105 /** 106 * Returns the {@link Parameter} at the index in the list, null if index is out of range. 107 * 108 * TODO: This call can cause memory leaks when used with the "as" operator until the following 109 * Vala bug is fixed (probably in version 0.19.1). 110 * [[https://bugzilla.gnome.org/show_bug.cgi?id=695671]] 111 */ 112 public new Parameter? get(int index) { 113 return ((index >= 0) && (index < list.size)) ? list.get(index) : null; 114 } 115 116 /** 117 * Returns the Parameter at the index. Throws an ImapError.TYPE_ERROR if the index is out of 118 * range. 119 * 120 * TODO: This call can cause memory leaks when used with the "as" operator until the following 121 * Vala bug is fixed (probably in version 0.19.1). 122 * [[https://bugzilla.gnome.org/show_bug.cgi?id=695671]] 123 */ 124 public Parameter get_required(int index) throws ImapError { 125 if ((index < 0) || (index >= list.size)) 126 throw new ImapError.TYPE_ERROR("No parameter at index %d", index); 127 128 Parameter? param = list.get(index); 129 if (param == null) 130 throw new ImapError.TYPE_ERROR("No parameter at index %d", index); 131 132 return param; 133 } 134 135 /** 136 * Returns {@link Parameter} at index if in range and of Type type, otherwise throws an 137 * {@link ImapError.TYPE_ERROR}. 138 * 139 * type must be of type Parameter. 140 */ 141 public Parameter get_as(int index, Type type) throws ImapError { 142 if (!type.is_a(typeof(Parameter))) 143 throw new ImapError.TYPE_ERROR("Attempting to cast non-Parameter at index %d", index); 144 145 Parameter param = get_required(index); 146 if (!param.get_type().is_a(type)) { 147 throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s (is %s)", index, 148 type.name(), param.get_type().name()); 149 } 150 151 return param; 152 } 153 154 /** 155 * Like {@link get_as}, but returns null if the {@link Parameter} at index is a 156 * {@link NilParameter}. 157 * 158 * type must be of type Parameter. 159 */ 160 public Parameter? get_as_nullable(int index, Type type) throws ImapError { 161 if (!type.is_a(typeof(Parameter))) 162 throw new ImapError.TYPE_ERROR("Attempting to cast non-Parameter at index %d", index); 163 164 Parameter param = get_required(index); 165 if (param is NilParameter) 166 return null; 167 168 // Because Deserializer doesn't produce NilParameters, check manually if this Parameter 169 // can legally be NIL according to IMAP grammar. 170 StringParameter? stringp = param as StringParameter; 171 if (stringp != null && NilParameter.is_nil(stringp)) 172 return null; 173 174 if (!param.get_type().is_a(type)) { 175 throw new ImapError.TYPE_ERROR("Parameter %d is not of type %s (is %s)", index, 176 type.name(), param.get_type().name()); 177 } 178 179 return param; 180 } 181 182 /** 183 * Like {@link get}, but returns null if {@link Parameter} at index is not of the specified type. 184 * 185 * type must be of type Parameter. 186 */ 187 public Parameter? get_if(int index, Type type) { 188 if (!type.is_a(typeof(Parameter))) 189 return null; 190 191 Parameter? param = get(index); 192 if (param == null || !param.get_type().is_a(type)) 193 return null; 194 195 return param; 196 } 197 198 // 199 // String retrieval 200 // 201 202 /** 203 * Returns a {@link StringParameter} only if the {@link Parameter} at index is a StringParameter. 204 * 205 * Compare to {@link get_as_nullable_string}. 206 */ 207 public StringParameter? get_if_string(int index) { 208 return (StringParameter?) get_if(index, typeof(StringParameter)); 209 } 210 211 /** 212 * Returns a {@link StringParameter} for the value at the index only if the {@link Parameter} 213 * is a StringParameter or a {@link LiteralParameter} with a length less than or equal to 214 * {@link MAX_STRING_LITERAL_LENGTH}. 215 * 216 * Because literal data is being coerced into a StringParameter, the result may not be suitable 217 * for transmission as-is. 218 * 219 * @see get_as_nullable_string 220 * @throws ImapError.TYPE_ERROR if no StringParameter at index or the literal is longer than 221 * MAX_STRING_LITERAL_LENGTH. 222 */ 223 public StringParameter get_as_string(int index) throws ImapError { 224 Parameter param = get_required(index); 225 226 StringParameter? stringp = param as StringParameter; 227 if (stringp != null) 228 return stringp; 229 230 LiteralParameter? literalp = param as LiteralParameter; 231 if (literalp != null && literalp.value.size <= MAX_STRING_LITERAL_LENGTH) 232 return literalp.coerce_to_string_parameter(); 233 234 throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index, 235 param.get_type().name()); 236 } 237 238 /** 239 * Returns a {@link StringParameter} for the value at the index only if the {@link Parameter} 240 * is a StringParameter or a {@link LiteralParameter} with a length less than or equal to 241 * {@link MAX_STRING_LITERAL_LENGTH}. 242 * 243 * Because literal data is being coerced into a StringParameter, the result may not be suitable 244 * for transmission as-is. 245 * 246 * @return null if no StringParameter or LiteralParameter at index. 247 * @see get_as_string 248 * @throws ImapError.TYPE_ERROR if literal is longer than MAX_STRING_LITERAL_LENGTH. 249 */ 250 public StringParameter? get_as_nullable_string(int index) throws ImapError { 251 Parameter? param = get_as_nullable(index, typeof(Parameter)); 252 if (param == null) 253 return null; 254 255 StringParameter? stringp = param as StringParameter; 256 if (stringp != null) 257 return stringp; 258 259 LiteralParameter? literalp = param as LiteralParameter; 260 if (literalp != null && literalp.value.size <= MAX_STRING_LITERAL_LENGTH) 261 return literalp.coerce_to_string_parameter(); 262 263 throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index, 264 param.get_type().name()); 265 } 266 267 /** 268 * Much like get_as_nullable_string() but returns an empty StringParameter (rather than null) 269 * if the parameter at index is a NilParameter. 270 */ 271 public StringParameter get_as_empty_string(int index) throws ImapError { 272 StringParameter? stringp = get_as_nullable_string(index); 273 274 return stringp ?? StringParameter.get_best_for(""); 275 } 276 277 // 278 // Number retrieval 279 // 280 281 /** 282 * Returns a {@link NumberParameter} at index, null if not of that type. 283 * 284 * @see get_if 285 */ 286 public NumberParameter? get_if_number(int index) { 287 return (NumberParameter?) get_if(index, typeof(NumberParameter)); 288 } 289 290 /** 291 * Returns a {@link NumberParameter} at index. 292 * 293 * Like {@link get_as_string}, this method will attempt some coercion. In this case, 294 * {@link QuotedStringParameter} and {@link UnquotedStringParameter}s will be converted to 295 * NumberParameter, if appropriate. 296 */ 297 public NumberParameter get_as_number(int index) throws ImapError { 298 Parameter param = get_required(index); 299 300 NumberParameter? numberp = param as NumberParameter; 301 if (numberp != null) 302 return numberp; 303 304 StringParameter? stringp = param as StringParameter; 305 if (stringp != null) { 306 numberp = stringp.coerce_to_number_parameter(); 307 if (numberp != null) 308 return numberp; 309 } 310 311 throw new ImapError.TYPE_ERROR("Parameter %d not of type number or string (is %s)", index, 312 param.get_type().name()); 313 } 314 315 // 316 // List retrieval 317 // 318 319 /** 320 * Returns a {@link ListParameter} at index. 321 * 322 * @see get_as 323 */ 324 public ListParameter get_as_list(int index) throws ImapError { 325 return (ListParameter) get_as(index, typeof(ListParameter)); 326 } 327 328 /** 329 * Returns a {@link ListParameter} at index, null if NIL. 330 * 331 * @see get_as_nullable 332 */ 333 public ListParameter? get_as_nullable_list(int index) throws ImapError { 334 return (ListParameter?) get_as_nullable(index, typeof(ListParameter)); 335 } 336 337 /** 338 * Returns {@link ListParameter} at index, an empty list if NIL. 339 */ 340 public ListParameter get_as_empty_list(int index) throws ImapError { 341 ListParameter? param = get_as_nullable_list(index); 342 343 return param ?? new ListParameter(); 344 } 345 346 /** 347 * Returns a {@link ListParameter} at index, null if not a list. 348 * 349 * @see get_if 350 */ 351 public ListParameter? get_if_list(int index) { 352 return (ListParameter?) get_if(index, typeof(ListParameter)); 353 } 354 355 // 356 // Literal retrieval 357 // 358 359 /** 360 * Returns a {@link LiteralParameter} at index. 361 * 362 * @see get_as 363 */ 364 public LiteralParameter get_as_literal(int index) throws ImapError { 365 return (LiteralParameter) get_as(index, typeof(LiteralParameter)); 366 } 367 368 /** 369 * Returns a {@link LiteralParameter} at index, null if NIL. 370 * 371 * @see get_as_nullable 372 */ 373 public LiteralParameter? get_as_nullable_literal(int index) throws ImapError { 374 return (LiteralParameter?) get_as_nullable(index, typeof(LiteralParameter)); 375 } 376 377 /** 378 * Returns a {@link LiteralParameter} at index, null if not a list. 379 * 380 * @see get_if 381 */ 382 public LiteralParameter? get_if_literal(int index) { 383 return (LiteralParameter?) get_if(index, typeof(LiteralParameter)); 384 } 385 386 /** 387 * Returns [@link LiteralParameter} at index, an empty list if NIL. 388 */ 389 public LiteralParameter get_as_empty_literal(int index) throws ImapError { 390 LiteralParameter? param = get_as_nullable_literal(index); 391 392 return param ?? new LiteralParameter(Geary.Memory.EmptyBuffer.instance); 393 } 394 395 /** 396 * Returns a {@link Memory.Buffer} for the {@link Parameter} at position index. 397 * 398 * Only converts {@link StringParameter} and {@link LiteralParameter}. All other types return 399 * null. 400 */ 401 public Memory.Buffer? get_as_nullable_buffer(int index) throws ImapError { 402 LiteralParameter? literalp = get_if_literal(index); 403 if (literalp != null) 404 return literalp.value; 405 406 StringParameter? stringp = get_if_string(index); 407 if (stringp != null) 408 return stringp.as_buffer(); 409 410 return null; 411 } 412 413 /** 414 * Returns a {@link Memory.Buffer} for the {@link Parameter} at position index. 415 * 416 * Only converts {@link StringParameter} and {@link LiteralParameter}. All other types return 417 * as an empty buffer. 418 */ 419 public Memory.Buffer get_as_empty_buffer(int index) throws ImapError { 420 return get_as_nullable_buffer(index) ?? Memory.EmptyBuffer.instance; 421 } 422 423 /** 424 * Returns a read-only List of {@link Parameter}s. 425 */ 426 public Gee.List<Parameter> get_all() { 427 return list.read_only_view; 428 } 429 430 /** 431 * Returns the replaced Parameter. Throws ImapError.TYPE_ERROR if no Parameter exists at the 432 * index. 433 */ 434 public Parameter replace(int index, Parameter parameter) throws ImapError { 435 if (list.size <= index) 436 throw new ImapError.TYPE_ERROR("No parameter at index %d", index); 437 438 Parameter old = this.list[index]; 439 this.list[index] = parameter; 440 return old; 441 } 442 443 /** 444 * Moves all child parameters from the supplied list into this list, clearing this list first. 445 * 446 * The supplied list will be "stripped" of its children. This ListParameter is cleared prior 447 * to adopting the new children. 448 */ 449 public void adopt_children(ListParameter src) { 450 clear(); 451 452 Gee.List<Parameter> src_children = new Gee.ArrayList<Parameter>(); 453 src_children.add_all(src.list); 454 src.clear(); 455 456 add_all(src_children); 457 } 458 459 protected string stringize_list() { 460 StringBuilder builder = new StringBuilder(); 461 462 int length = list.size; 463 for (int ctr = 0; ctr < length; ctr++) { 464 builder.append(list[ctr].to_string()); 465 if (ctr < (length - 1)) 466 builder.append_c(' '); 467 } 468 469 return builder.str; 470 } 471 472 /** 473 * {@inheritDoc} 474 */ 475 public override string to_string() { 476 return "(%s)".printf(stringize_list()); 477 } 478 479 /** 480 * {@inheritDoc} 481 */ 482 public override void serialize(Serializer ser, GLib.Cancellable cancellable) 483 throws GLib.Error { 484 ser.push_ascii('(', cancellable); 485 serialize_list(ser, cancellable); 486 ser.push_ascii(')', cancellable); 487 } 488 489 protected void serialize_list(Serializer ser, GLib.Cancellable cancellable) 490 throws GLib.Error { 491 int length = list.size; 492 for (int ctr = 0; ctr < length; ctr++) { 493 list[ctr].serialize(ser, cancellable); 494 if (ctr < (length - 1)) 495 ser.push_space(cancellable); 496 } 497 } 498 499} 500