-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
// Copyright 2025 Shota FUJI
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
namespace Moo {
public errordomain HeadersParseError {
NO_PARSING_CONTEXT,
NO_DELIMITER,
EMPTY_KEY,
NON_ASCII_KEY,
NON_UTF8_VALUE,
}
public class Headers : Object {
private Gee.HashMap<string, Gee.ArrayList<string> >map;
public int last_byte_index { get; construct; default = -1; }
public int size { get { return map.size; } }
public string? content_type {
owned get {
var entry = map["Content-Type"];
if (entry == null) {
return null;
}
return entry[0];
}
}
public uint64 content_length {
get {
var entry = map["Content-Length"];
if (entry == null) {
return 0;
}
var value = entry[0];
if (value == null) {
return 0;
}
return uint64.parse(value);
}
}
public uint64 request_id {
get {
var entry = map["Request-Id"];
if (entry == null) {
return 0;
}
var value = entry[0];
if (value == null) {
return 0;
}
return uint64.parse(value);
}
}
public Headers() {
Object();
this.map = new Gee.HashMap<string, Gee.ArrayList<string> >();
}
/**
* Parses `src`.
*
* Header keys won't be lowercased or uppercased: Roon server treats
* those header keys in a case sensitive way.
*
* Throws an `NO_PARSING_CONTEXT` if `meta` is not created by
* `Metadata.from_string()`.
*/
public Headers.from_string(string src, Metadata meta) throws HeadersParseError {
if (meta.last_byte_index < 0) {
throw new HeadersParseError.NO_PARSING_CONTEXT("Metadata does not contain parsed length.");
}
var map = new Gee.HashMap<string, Gee.ArrayList<string> >();
int i = meta.last_byte_index;
while (i + 1 <= src.length) {
var lf = src.index_of_char('\n', i);
if (lf == i) {
i += 1;
break;
}
var line = src.slice(i, lf < 0 ? src.length : lf);
i = lf < 0 ? i + line.length : lf + 1;
var parts = line.split(":", 2);
if (parts.length < 2) {
throw new HeadersParseError.NO_DELIMITER("Header delimiter does not found.");
}
var key = parts[0].strip();
if (key.length == 0) {
throw new HeadersParseError.EMPTY_KEY("Found an empty header key.");
}
if (!key.is_ascii()) {
throw new HeadersParseError.NON_ASCII_KEY("Found a header key containing non-ASCII character.");
}
var value = parts[1].strip();
if (!value.validate()) {
throw new HeadersParseError.NON_UTF8_VALUE("Found a header with non-UTF8 value.");
}
var existing = map[key];
if (existing != null) {
existing.add(value);
} else {
var entry = new Gee.ArrayList<string>();
entry.add(value);
map[key] = entry;
}
}
Object(last_byte_index: i);
this.map = map;
}
public new Gee.ArrayList<string>? @get(string key) {
return map[key];
}
public string to_string() {
var builder = new GLib.StringBuilder();
foreach (var entry in map) {
foreach (var value in entry.value) {
builder.append_printf("%s: %s\n", entry.key, value);
}
}
builder.append("\n");
return builder.str;
}
public void write(string key, string value) {
var existing = map[key];
if (existing != null) {
existing.add(value);
} else {
var entry = new Gee.ArrayList<string>();
entry.add(value);
map[key] = entry;
}
}
}
}