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