1 module hunt.http.codec.http.model.HttpFields;
2 
3 import hunt.http.codec.http.model.HttpField;
4 import hunt.http.codec.http.model.HttpHeader;
5 import hunt.http.codec.http.model.HttpHeaderValue;
6 import hunt.http.codec.http.model.QuotedCSV;
7 
8 import hunt.container;
9 import hunt.lang.exception;
10 import hunt.logging;
11 import hunt.string;
12 
13 import std.array;
14 import std.container.array;
15 import std.conv;
16 import std.datetime;
17 import std.string;
18 import std.range;
19 
20 
21 /**
22  * HTTP Fields. A collection of HTTP header and or Trailer fields.
23  *
24  * <p>
25  * This class is not synchronized as it is expected that modifications will only
26  * be performed by a single thread.
27  * 
28  * <p>
29  * The cookie handling provided by this class is guided by the Servlet
30  * specification and RFC6265.
31  *
32  */
33 class HttpFields : Iterable!HttpField {
34 	// static string __separators = ", \t";
35 
36 	private HttpField[] _fields;
37 	private int _size;
38 
39 	/**
40 	 * Initialize an empty HttpFields.
41 	 */
42 	this() {
43 		_fields = new HttpField[20];
44 	}
45 
46 	/**
47 	 * Initialize an empty HttpFields.
48 	 * 
49 	 * @param capacity
50 	 *            the capacity of the http fields
51 	 */
52 	this(int capacity) {
53 		_fields = new HttpField[capacity];
54 	}
55 
56 	/**
57 	 * Initialize HttpFields from copy.
58 	 * 
59 	 * @param fields
60 	 *            the fields to copy data from
61 	 */
62 	this(HttpFields fields) {
63 		_fields = fields._fields.dup ~ new HttpField[10];
64 		_size = fields.size();
65 	}
66 
67 	int size() {
68 		return _size;
69 	}
70 
71 	InputRange!HttpField iterator() {
72 		return inputRangeObject(_fields[0 .. _size]);
73 	}
74 
75 	int opApply(scope int delegate(ref HttpField) dg) {
76 		int result = 0;
77 		foreach (HttpField v; _fields[0 .. _size]) {
78 			result = dg(v);
79 			if (result != 0)
80 				return result;
81 		}
82 		return result;
83 	}
84 
85 	/**
86 	 * Get Collection of header names.
87 	 * 
88 	 * @return the unique set of field names.
89 	 */
90 	Set!string getFieldNamesCollection() {
91 		Set!string set = new HashSet!string(_size);
92 		foreach (HttpField f; _fields[0 .. _size]) {
93 			if (f !is null)
94 				set.add(f.getName());
95 		}
96 		return set;
97 	}
98 
99 	/**
100 	 * Get enumeration of header _names. Returns an enumeration of strings
101 	 * representing the header _names for this request.
102 	 * 
103 	 * @return an enumeration of field names
104 	 */
105 	InputRange!string getFieldNames() {
106 		bool[string] set;
107 		foreach (HttpField f; _fields[0 .. _size]) {
108 			if (f !is null)
109 				set[f.getName()] = true;
110 		}
111 		return inputRangeObject(set.keys);
112 	}
113 
114 	// InputRange!string getFieldNames() {
115 	// 	// return Collections.enumeration(getFieldNamesCollection());
116 	// 	// return getFieldNamesCollection().toArray();
117 	// 	Array!string set;
118 	// 	foreach (HttpField f ; _fields[0.._size]) {
119 	// 		if (f !is null)
120 	// 			set.insertBack(f.getName());
121 	// 	}
122 	// 	// Enumeration!string r = new RangeEnumeration!string(inputRangeObject(set[].array));
123 	// 	return inputRangeObject(set[].array);
124 	// }
125 
126 	/**
127 	 * Get a Field by index.
128 	 * 
129 	 * @param index
130 	 *            the field index
131 	 * @return A Field value or null if the Field value has not been set
132 	 */
133 	HttpField getField(int index) {
134 		if (index >= _size)
135 			throw new NoSuchElementException("");
136 		return _fields[index];
137 	}
138 
139 	HttpField getField(HttpHeader header) {
140 		for (int i = 0; i < _size; i++) {
141 			HttpField f = _fields[i];
142 			if (f.getHeader() == header)
143 				return f;
144 		}
145 		return null;
146 	}
147 
148 	HttpField getField(string name) {
149 		for (int i = 0; i < _size; i++) {
150 			HttpField f = _fields[i];
151 			if (f.getName().equalsIgnoreCase(name))
152 				return f;
153 		}
154 		return null;
155 	}
156 
157 	bool contains(HttpField field) {
158 		for (int i = _size; i-- > 0;) {
159 			HttpField f = _fields[i];
160 			if (f.isSameName(field) && (f.opEquals(field) || f.contains(field.getValue())))
161 				return true;
162 		}
163 		return false;
164 	}
165 
166 	bool contains(HttpHeader header, string value) {
167 		for (int i = _size; i-- > 0;) {
168 			HttpField f = _fields[i];
169 			if (f.getHeader() == header && f.contains(value))
170 				return true;
171 		}
172 		return false;
173 	}
174 
175 	bool contains(string name, string value) {
176 		for (int i = _size; i-- > 0;) {
177 			HttpField f = _fields[i];
178 			if (f.getName().equalsIgnoreCase(name) && f.contains(value))
179 				return true;
180 		}
181 		return false;
182 	}
183 
184 	bool contains(HttpHeader header) {
185 		for (int i = _size; i-- > 0;) {
186 			HttpField f = _fields[i];
187 			if (f.getHeader() == header)
188 				return true;
189 		}
190 		return false;
191 	}
192 
193 	bool containsKey(string name) {
194 		for (int i = _size; i-- > 0;) {
195 			HttpField f = _fields[i];
196 			if (std..string.icmp(f.getName(), name) == 0)
197 				return true;
198 		}
199 		return false;
200 	}
201 
202 	string get(HttpHeader header) {
203 		for (int i = 0; i < _size; i++) {
204 			HttpField f = _fields[i];
205 			if (f.getHeader() == header)
206 				return f.getValue();
207 		}
208 		return null;
209 	}
210 
211 	string get(string header) {
212 		for (int i = 0; i < _size; i++) {
213 			HttpField f = _fields[i];
214 			if (f.getName().equalsIgnoreCase(header))
215 				return f.getValue();
216 		}
217 		return null;
218 	}
219 
220 	/**
221 	 * Get multiple header of the same name
222 	 *
223 	 * @return List the values
224 	 * @param header
225 	 *            the header
226 	 */
227 	string[] getValuesList(HttpHeader header) {
228 		Array!(string) list;
229 		foreach (HttpField f; this)
230 			if (f.getHeader() == header)
231 				list.insertBack(f.getValue());
232 		return list.array();
233 	}
234 
235 	/**
236 	 * Get multiple header of the same name
237 	 * 
238 	 * @return List the header values
239 	 * @param name
240 	 *            the case-insensitive field name
241 	 */
242 	string[] getValuesList(string name) {
243 		Array!(string) list;
244 		foreach (HttpField f; this)
245 			if (f.getName().equalsIgnoreCase(name))
246 				list.insertBack(f.getValue());
247 		return list.array();
248 	}
249 
250 	/**
251 	 * Add comma separated values, but only if not already present.
252 	 * 
253 	 * @param header
254 	 *            The header to add the value(s) to
255 	 * @param values
256 	 *            The value(s) to add
257 	 * @return True if headers were modified
258 	 */
259 	// bool addCSV(HttpHeader header, string... values) {
260 	// 	QuotedCSV existing = null;
261 	// 	for (HttpField f : this) {
262 	// 		if (f.getHeader() == header) {
263 	// 			if (existing == null)
264 	// 				existing = new QuotedCSV(false);
265 	// 			existing.addValue(f.getValue());
266 	// 		}
267 	// 	}
268 
269 	// 	string value = addCSV(existing, values);
270 	// 	if (value != null) {
271 	// 		add(header, value);
272 	// 		return true;
273 	// 	}
274 	// 	return false;
275 	// }
276 
277 	/**
278 	 * Add comma separated values, but only if not already present.
279 	 * 
280 	 * @param name
281 	 *            The header to add the value(s) to
282 	 * @param values
283 	 *            The value(s) to add
284 	 * @return True if headers were modified
285 	 */
286 	bool addCSV(string name, string[] values...) {
287 		QuotedCSV existing = null;
288 		foreach (HttpField f; this) {
289 			if (f.getName().equalsIgnoreCase(name)) {
290 				if (existing is null)
291 					existing = new QuotedCSV(false);
292 				existing.addValue(f.getValue());
293 			}
294 		}
295 		string value = addCSV(existing, values);
296 		if (value != null) {
297 			add(name, value);
298 			return true;
299 		}
300 		return false;
301 	}
302 
303 	protected string addCSV(QuotedCSV existing, string[] values...) {
304 		// remove any existing values from the new values
305 		bool add = true;
306 		if (existing !is null && !existing.isEmpty()) {
307 			add = false;
308 
309 			for (size_t i = values.length; i-- > 0;) {
310 				string unquoted = QuotedCSV.unquote(values[i]);
311 				if (existing.getValues().contains(unquoted))
312 					values[i] = null;
313 				else
314 					add = true;
315 			}
316 		}
317 
318 		if (add) {
319 			StringBuilder value = new StringBuilder();
320 			foreach (string v; values) {
321 				if (v == null)
322 					continue;
323 				if (value.length > 0)
324 					value.append(", ");
325 				value.append(v);
326 			}
327 			if (value.length > 0)
328 				return value.toString();
329 		}
330 
331 		return null;
332 	}
333 
334 	/**
335 	 * Get multiple field values of the same name, split as a {@link QuotedCSV}
336 	 *
337 	 * @return List the values with OWS stripped
338 	 * @param header
339 	 *            The header
340 	 * @param keepQuotes
341 	 *            True if the fields are kept quoted
342 	 */
343 	string[] getCSV(HttpHeader header, bool keepQuotes) {
344 		QuotedCSV values = null;
345 		foreach (HttpField f; _fields[0 .. _size]) {
346 			if (f.getHeader() == header) {
347 				if (values is null)
348 					values = new QuotedCSV(keepQuotes);
349 				values.addValue(f.getValue());
350 			}
351 		}
352 		// Array!string ar = values.getValues();
353 		// return inputRangeObject(values.getValues()[].array);
354 
355 		return values is null ? cast(string[]) null : values.getValues().array;
356 	}
357 
358 	/**
359 	 * Get multiple field values of the same name as a {@link QuotedCSV}
360 	 *
361 	 * @return List the values with OWS stripped
362 	 * @param name
363 	 *            the case-insensitive field name
364 	 * @param keepQuotes
365 	 *            True if the fields are kept quoted
366 	 */
367 	List!string getCSV(string name, bool keepQuotes) {
368 		QuotedCSV values = null;
369 		foreach (HttpField f; _fields[0 .. _size]) {
370 			if (f.getName().equalsIgnoreCase(name)) {
371 				if (values is null)
372 					values = new QuotedCSV(keepQuotes);
373 				values.addValue(f.getValue());
374 			}
375 		}
376 		return values is null ? null : new ArrayList!string(values.getValues().array);
377 		// return inputRangeObject(values.getValues()[].array);
378 	}
379 
380 	string[] getCsvAsArray(string name, bool keepQuotes) {
381 		QuotedCSV values = null;
382 		foreach (HttpField f; _fields[0 .. _size]) {
383 			if (f.getName().equalsIgnoreCase(name)) {
384 				if (values is null)
385 					values = new QuotedCSV(keepQuotes);
386 				values.addValue(f.getValue());
387 			}
388 		}
389 		return values is null ? null : values.getValues().array;
390 		// return inputRangeObject(values.getValues()[].array);
391 	}
392 
393 	/**
394 	 * Get multiple field values of the same name, split and sorted as a
395 	 * {@link QuotedQualityCSV}
396 	 *
397 	 * @return List the values in quality order with the q param and OWS
398 	 *         stripped
399 	 * @param header
400 	 *            The header
401 	 */
402 	// List!string getQualityCSV(HttpHeader header) {
403 	// 	QuotedQualityCSV values = null;
404 	// 	for (HttpField f : this) {
405 	// 		if (f.getHeader() == header) {
406 	// 			if (values == null)
407 	// 				values = new QuotedQualityCSV();
408 	// 			values.addValue(f.getValue());
409 	// 		}
410 	// 	}
411 
412 	// 	return values == null ? Collections.emptyList() : values.getValues();
413 	// }
414 
415 	/**
416 	 * Get multiple field values of the same name, split and sorted as a
417 	 * {@link QuotedQualityCSV}
418 	 *
419 	 * @return List the values in quality order with the q param and OWS
420 	 *         stripped
421 	 * @param name
422 	 *            the case-insensitive field name
423 	 */
424 	// List!string getQualityCSV(string name) {
425 	// 	QuotedQualityCSV values = null;
426 	// 	for (HttpField f : this) {
427 	// 		if (f.getName().equalsIgnoreCase(name)) {
428 	// 			if (values == null)
429 	// 				values = new QuotedQualityCSV();
430 	// 			values.addValue(f.getValue());
431 	// 		}
432 	// 	}
433 	// 	return values == null ? Collections.emptyList() : values.getValues();
434 	// }
435 
436 	/**
437 	 * Get multi headers
438 	 *
439 	 * @return Enumeration of the values
440 	 * @param name
441 	 *            the case-insensitive field name
442 	 */
443 	InputRange!string getValues(string name) {
444 		Array!string r;
445 
446 		for (int i = 0; i < _size; i++) {
447 			HttpField f = _fields[i];
448 			if (f.getName().equalsIgnoreCase(name)) {
449 				string v = f.getValue();
450 				if (!v.empty)
451 					r.insertBack(v);
452 			}
453 		}
454 
455 		return inputRangeObject(r[].array);
456 	}
457 
458 	void put(HttpField field) {
459 		bool put = false;
460 		for (int i = _size; i-- > 0;) {
461 			HttpField f = _fields[i];
462 			if (f.isSameName(field)) {
463 				if (put) {
464 					--_size;
465 					_fields[i + 1 .. _size + 1] = _fields[i .. _size];
466 				} else {
467 					_fields[i] = field;
468 					put = true;
469 				}
470 			}
471 		}
472 		if (!put)
473 			add(field);
474 	}
475 
476 	/**
477 	 * Set a field.
478 	 *
479 	 * @param name
480 	 *            the name of the field
481 	 * @param value
482 	 *            the value of the field. If null the field is cleared.
483 	 */
484 	void put(string name, string value) {
485 		if (value == null)
486 			remove(name);
487 		else
488 			put(new HttpField(name, value));
489 	}
490 
491 	void put(HttpHeader header, HttpHeaderValue value) {
492 		put(header, value.toString());
493 	}
494 
495 	/**
496 	 * Set a field.
497 	 *
498 	 * @param header
499 	 *            the header name of the field
500 	 * @param value
501 	 *            the value of the field. If null the field is cleared.
502 	 */
503 	void put(HttpHeader header, string value) {
504 		if (value == null)
505 			remove(header);
506 		else
507 			put(new HttpField(header, value));
508 	}
509 
510 	/**
511 	 * Set a field.
512 	 *
513 	 * @param name
514 	 *            the name of the field
515 	 * @param list
516 	 *            the List value of the field. If null the field is cleared.
517 	 */
518 	void put(string name, List!string list) {
519 		remove(name);
520 		foreach (string v; list)
521 			if (!v.empty)
522 				add(name, v);
523 	}
524 
525 	/**
526 	 * Add to or set a field. If the field is allowed to have multiple values,
527 	 * add will add multiple headers of the same name.
528 	 *
529 	 * @param name
530 	 *            the name of the field
531 	 * @param value
532 	 *            the value of the field.
533 	 */
534 	void add(string name, string value) {
535 		HttpField field = new HttpField(name, value);
536 		add(field);
537 	}
538 
539 	void add(HttpHeader header, HttpHeaderValue value) {
540 		add(header, value.toString());
541 	}
542 
543 	/**
544 	 * Add to or set a field. If the field is allowed to have multiple values,
545 	 * add will add multiple headers of the same name.
546 	 *
547 	 * @param header
548 	 *            the header
549 	 * @param value
550 	 *            the value of the field.
551 	 */
552 	void add(HttpHeader header, string value) {
553 		if (value.empty)
554 			throw new IllegalArgumentException("null value");
555 
556 		HttpField field = new HttpField(header, value);
557 		add(field);
558 	}
559 
560 	/**
561 	 * Remove a field.
562 	 *
563 	 * @param name
564 	 *            the field to remove
565 	 * @return the header that was removed
566 	 */
567 	HttpField remove(HttpHeader name) {
568 
569 		HttpField removed = null;
570 		for (int i = _size; i-- > 0;) {
571 			HttpField f = _fields[i];
572 			if (f.getHeader() == name) {
573 				removed = f;
574 				--_size;
575 				for (int j = i; j < size; j++)
576 					_fields[j] = _fields[j + 1];
577 			}
578 		}
579 		return removed;
580 	}
581 
582 	/**
583 	 * Remove a field.
584 	 *
585 	 * @param name
586 	 *            the field to remove
587 	 * @return the header that was removed
588 	 */
589 	HttpField remove(string name) {
590 		HttpField removed = null;
591 		for (int i = _size; i-- > 0;) {
592 			HttpField f = _fields[i];
593 			if (f.getName().equalsIgnoreCase(name)) {
594 				removed = f;
595 				--_size;
596 				for (int j = i; j < size; j++)
597 					_fields[j] = _fields[j + 1];
598 			}
599 		}
600 		return removed;
601 	}
602 
603 	/**
604 	 * Get a header as an long value. Returns the value of an integer field or
605 	 * -1 if not found. The case of the field name is ignored.
606 	 *
607 	 * @param name
608 	 *            the case-insensitive field name
609 	 * @return the value of the field as a long
610 	 * @exception NumberFormatException
611 	 *                If bad long found
612 	 */
613 	long getLongField(string name) {
614 		HttpField field = getField(name);
615 		return field is null ? -1L : field.getLongValue();
616 	}
617 
618 	/**
619 	 * Get a header as a date value. Returns the value of a date field, or -1 if
620 	 * not found. The case of the field name is ignored.
621 	 *
622 	 * @param name
623 	 *            the case-insensitive field name
624 	 * @return the value of the field as a number of milliseconds since unix
625 	 *         epoch
626 	 */
627 	long getDateField(string name) {
628 		HttpField field = getField(name);
629 		if (field is null)
630 			return -1;
631 
632 		string val = valueParameters(field.getValue(), null);
633 		if (val.empty)
634 			return -1;
635 
636 		// TODO: Tasks pending completion -@zxp at 6/21/2018, 10:59:24 AM
637 		// 
638 		long date = SysTime.fromISOExtString(val).stdTime(); // DateParser.parseDate(val);
639 		if (date == -1)
640 			throw new IllegalArgumentException("Cannot convert date: " ~ val);
641 		return date;
642 	}
643 
644 	/**
645 	 * Sets the value of an long field.
646 	 *
647 	 * @param name
648 	 *            the field name
649 	 * @param value
650 	 *            the field long value
651 	 */
652 	void putLongField(HttpHeader name, long value) {
653 		string v = to!string(value);
654 		put(name, v);
655 	}
656 
657 	/**
658 	 * Sets the value of an long field.
659 	 *
660 	 * @param name
661 	 *            the field name
662 	 * @param value
663 	 *            the field long value
664 	 */
665 	void putLongField(string name, long value) {
666 		string v = to!string(value);
667 		put(name, v);
668 	}
669 
670 	/**
671 	 * Sets the value of a date field.
672 	 *
673 	 * @param name
674 	 *            the field name
675 	 * @param date
676 	 *            the field date value
677 	 */
678 	void putDateField(HttpHeader name, long date) {
679 		// TODO: Tasks pending completion -@zxp at 6/21/2018, 10:42:44 AM
680 		// 
681 		// string d = DateGenerator.formatDate(date);
682 		string d = SysTime(date).toISOExtString();
683 		put(name, d);
684 	}
685 
686 	/**
687 	 * Sets the value of a date field.
688 	 *
689 	 * @param name
690 	 *            the field name
691 	 * @param date
692 	 *            the field date value
693 	 */
694 	void putDateField(string name, long date) {
695 		// TODO: Tasks pending completion -@zxp at 6/21/2018, 11:04:46 AM
696 		// 
697 		// string d = DateGenerator.formatDate(date);		
698 		string d = SysTime(date).toISOExtString();
699 		put(name, d);
700 	}
701 
702 	/**
703 	 * Sets the value of a date field.
704 	 *
705 	 * @param name
706 	 *            the field name
707 	 * @param date
708 	 *            the field date value
709 	 */
710 	void addDateField(string name, long date) {
711 		// string d = DateGenerator.formatDate(date);
712 		string d = SysTime(date).toISOExtString();
713 		add(name, d);
714 	}
715 
716 	override size_t toHash() @trusted nothrow {
717 		int hash = 0;
718 		foreach (HttpField field; _fields[0 .. _size])
719 			hash += field.toHash();
720 		return hash;
721 	}
722 
723 	override bool opEquals(Object o) {
724 		if (o is this)
725 			return true;
726 
727 		if (!object.opEquals(this, o))
728 			return false;
729 		HttpFields that = cast(HttpFields) o;
730 		if (that is null)
731 			return false;
732 
733 		// Order is not important, so we cannot rely on List.equals().
734 		if (size() != that.size())
735 			return false;
736 
737 		foreach (HttpField fi; this) {
738 			bool isContinue = false;
739 			foreach (HttpField fa; that) {
740 				if (fi == fa) {
741 					isContinue = true;
742 					break;
743 				}
744 			}
745 			if (!isContinue)
746 				return false;
747 		}
748 		return true;
749 	}
750 
751 	override string toString() {
752 		try {
753 			StringBuilder buffer = new StringBuilder();
754 			foreach (HttpField field; this) {
755 				if (field !is null) {
756 					string tmp = field.getName();
757 					if (tmp != null)
758 						buffer.append(tmp);
759 					buffer.append(": ");
760 					tmp = field.getValue();
761 					if (tmp != null)
762 						buffer.append(tmp);
763 					buffer.append("\r\n");
764 				}
765 			}
766 			buffer.append("\r\n");
767 			return buffer.toString();
768 		} catch (Exception e) {
769 			warningf("http fields toString exception", e);
770 			return e.toString();
771 		}
772 	}
773 
774 	void clear() {
775 		_size = 0;
776 	}
777 
778 	void add(HttpField field) {
779 		if (field !is null) {
780 			if (_size == _fields.length)
781 				_fields = _fields.dup ~ new HttpField[_size];
782 			_fields[_size++] = field;
783 		}
784 	}
785 
786 	void addAll(HttpFields fields) {
787 		for (int i = 0; i < fields._size; i++)
788 			add(fields._fields[i]);
789 	}
790 
791 	/**
792 	 * Add fields from another HttpFields instance. Single valued fields are
793 	 * replaced, while all others are added.
794 	 *
795 	 * @param fields
796 	 *            the fields to add
797 	 */
798 	void add(HttpFields fields) {
799 		if (fields is null)
800 			return;
801 
802 		// Enumeration<string> e = fields.getFieldNames();
803 		// while (e.hasMoreElements()) {
804 		// 	string name = e.nextElement();
805 		// 	Enumeration<string> values = fields.getValues(name);
806 		// 	while (values.hasMoreElements())
807 		// 		add(name, values.nextElement());
808 		// }
809 
810 		auto fieldNames = fields.getFieldNames();
811 		foreach (string n; fieldNames) {
812 			auto values = fields.getValues(n);
813 			foreach (string v; values)
814 				add(n, v);
815 		}
816 	}
817 
818 	/**
819 	 * Get field value without parameters. Some field values can have
820 	 * parameters. This method separates the value from the parameters and
821 	 * optionally populates a map with the parameters. For example:
822 	 *
823 	 * <PRE>
824 	 *
825 	 * FieldName : Value ; param1=val1 ; param2=val2
826 	 *
827 	 * </PRE>
828 	 *
829 	 * @param value
830 	 *            The Field value, possibly with parameters.
831 	 * @return The value.
832 	 */
833 	static string stripParameters(string value) {
834 		if (value == null)
835 			return null;
836 
837 		int i = cast(int) value.indexOf(';');
838 		if (i < 0)
839 			return value;
840 		return value.substring(0, i).strip();
841 	}
842 
843 	/**
844 	 * Get field value parameters. Some field values can have parameters. This
845 	 * method separates the value from the parameters and optionally populates a
846 	 * map with the parameters. For example:
847 	 *
848 	 * <PRE>
849 	 *
850 	 * FieldName : Value ; param1=val1 ; param2=val2
851 	 *
852 	 * </PRE>
853 	 *
854 	 * @param value
855 	 *            The Field value, possibly with parameters.
856 	 * @param parameters
857 	 *            A map to populate with the parameters, or null
858 	 * @return The value.
859 	 */
860 	static string valueParameters(string value, Map!(string, string) parameters) {
861 		if (value is null)
862 			return null;
863 
864 		int i = cast(int) value.indexOf(';');
865 		if (i < 0)
866 			return value;
867 		if (parameters is null)
868 			return value.substring(0, i).strip();
869 
870 		StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
871 		while (tok1.hasMoreTokens()) {
872 			string token = tok1.nextToken();
873 			StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
874 			if (tok2.hasMoreTokens()) {
875 				string paramName = tok2.nextToken();
876 				string paramVal = null;
877 				if (tok2.hasMoreTokens())
878 					paramVal = tok2.nextToken();
879 				parameters.put(paramName, paramVal);
880 			}
881 		}
882 
883 		return value.substring(0, i).strip();
884 	}
885 }