/src/wireshark/epan/dissectors/packet-cups.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* packet-cups.c |
2 | | * Routines for Common Unix Printing System (CUPS) Browsing Protocol |
3 | | * packet disassembly for the Wireshark network traffic analyzer. |
4 | | * |
5 | | * Charles Levert <charles@comm.polymtl.ca> |
6 | | * Copyright 2001 Charles Levert |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | * |
10 | | */ |
11 | | |
12 | | #include "config.h" |
13 | | |
14 | | #include <epan/packet.h> |
15 | | #include <epan/tfs.h> |
16 | | #include <wsutil/str_util.h> |
17 | | |
18 | | /**********************************************************************/ |
19 | | |
20 | | void proto_register_cups(void); |
21 | | void proto_reg_handoff_cups(void); |
22 | | |
23 | | static dissector_handle_t cups_handle; |
24 | | |
25 | | /* From cups/cups.h, GNU GPL, Copyright 1997-2001 by Easy Software Products. */ |
26 | | typedef uint32_t cups_ptype_t; /**** Printer Type/Capability Bits ****/ |
27 | | enum /* Not a typedef'd enum so we can OR */ |
28 | | { |
29 | | CUPS_PRINTER_LOCAL = 0x0000, /* Local printer or class */ |
30 | | CUPS_PRINTER_CLASS = 0x0001, /* Printer class */ |
31 | | CUPS_PRINTER_REMOTE = 0x0002, /* Remote printer or class */ |
32 | | CUPS_PRINTER_BW = 0x0004, /* Can do B&W printing */ |
33 | | CUPS_PRINTER_COLOR = 0x0008, /* Can do color printing */ |
34 | | CUPS_PRINTER_DUPLEX = 0x0010, /* Can do duplexing */ |
35 | | CUPS_PRINTER_STAPLE = 0x0020, /* Can staple output */ |
36 | | CUPS_PRINTER_COPIES = 0x0040, /* Can do copies */ |
37 | | CUPS_PRINTER_COLLATE = 0x0080, /* Can collage copies */ |
38 | | CUPS_PRINTER_PUNCH = 0x0100, /* Can punch output */ |
39 | | CUPS_PRINTER_COVER = 0x0200, /* Can cover output */ |
40 | | CUPS_PRINTER_BIND = 0x0400, /* Can bind output */ |
41 | | CUPS_PRINTER_SORT = 0x0800, /* Can sort output */ |
42 | | CUPS_PRINTER_SMALL = 0x1000, /* Can do Letter/Legal/A4 */ |
43 | | CUPS_PRINTER_MEDIUM = 0x2000, /* Can do Tabloid/B/C/A3/A2 */ |
44 | | CUPS_PRINTER_LARGE = 0x4000, /* Can do D/E/A1/A0 */ |
45 | | CUPS_PRINTER_VARIABLE = 0x8000, /* Can do variable sizes */ |
46 | | CUPS_PRINTER_IMPLICIT = 0x10000, /* Implicit class */ |
47 | | CUPS_PRINTER_DEFAULT = 0x20000, /* Default printer on network */ |
48 | | CUPS_PRINTER_OPTIONS = 0xfffc /* ~(CLASS | REMOTE | IMPLICIT) */ |
49 | | }; |
50 | | /* End insert from cups/cups.h */ |
51 | | |
52 | | typedef enum _cups_state { |
53 | | CUPS_IDLE = 3, |
54 | | CUPS_PROCESSING, |
55 | | CUPS_STOPPED |
56 | | } cups_state_t; |
57 | | |
58 | | static const value_string cups_state_values[] = { |
59 | | { CUPS_IDLE, "idle" }, |
60 | | { CUPS_PROCESSING, "processing" }, |
61 | | { CUPS_STOPPED, "stopped" }, |
62 | | { 0, NULL } |
63 | | }; |
64 | | |
65 | | static const true_false_string tfs_implicit_explicit = { "Implicit class", "Explicit class" }; |
66 | | static const true_false_string tfs_printer_class = { "Printer class", "Single printer" }; |
67 | | |
68 | | static int proto_cups; |
69 | | static int hf_cups_ptype; |
70 | | static int hf_cups_ptype_default; |
71 | | static int hf_cups_ptype_implicit; |
72 | | static int hf_cups_ptype_variable; |
73 | | static int hf_cups_ptype_large; |
74 | | static int hf_cups_ptype_medium; |
75 | | static int hf_cups_ptype_small; |
76 | | static int hf_cups_ptype_sort; |
77 | | static int hf_cups_ptype_bind; |
78 | | static int hf_cups_ptype_cover; |
79 | | static int hf_cups_ptype_punch; |
80 | | static int hf_cups_ptype_collate; |
81 | | static int hf_cups_ptype_copies; |
82 | | static int hf_cups_ptype_staple; |
83 | | static int hf_cups_ptype_duplex; |
84 | | static int hf_cups_ptype_color; |
85 | | static int hf_cups_ptype_bw; |
86 | | static int hf_cups_ptype_remote; |
87 | | static int hf_cups_ptype_class; |
88 | | static int hf_cups_state; |
89 | | static int hf_cups_uri; |
90 | | static int hf_cups_location; |
91 | | static int hf_cups_information; |
92 | | static int hf_cups_make_model; |
93 | | |
94 | | static int ett_cups; |
95 | | static int ett_cups_ptype; |
96 | | |
97 | | /* patterns used for tvb_ws_mempbrk_pattern_uint8 */ |
98 | | static ws_mempbrk_pattern pbrk_whitespace; |
99 | | |
100 | | /* This protocol is heavily related to IPP, but it is CUPS-specific |
101 | | and non-standard. */ |
102 | 14 | #define UDP_PORT_CUPS 631 |
103 | 7 | #define PROTO_TAG_CUPS "CUPS" |
104 | | |
105 | | static unsigned get_hex_uint(tvbuff_t *tvb, int offset, int *next_offset); |
106 | | static bool skip_space(tvbuff_t *tvb, int offset, int *next_offset); |
107 | | static const uint8_t* get_quoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, int offset, |
108 | | int *next_offset, unsigned *len); |
109 | | static const uint8_t* get_unquoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, int offset, |
110 | | int *next_offset, unsigned *len); |
111 | | |
112 | | /**********************************************************************/ |
113 | | |
114 | | static int |
115 | | dissect_cups(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
116 | 7 | { |
117 | 7 | proto_tree *cups_tree = NULL; |
118 | 7 | proto_tree *ptype_subtree = NULL; |
119 | 7 | proto_item *ti = NULL; |
120 | 7 | int offset = 0; |
121 | 7 | int next_offset; |
122 | 7 | unsigned len; |
123 | 7 | const uint8_t *str; |
124 | 7 | cups_ptype_t ptype; |
125 | 7 | unsigned int state; |
126 | | |
127 | 7 | col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CUPS); |
128 | 7 | col_clear(pinfo->cinfo, COL_INFO); |
129 | | |
130 | 7 | ti = proto_tree_add_item(tree, proto_cups, tvb, offset, -1, ENC_NA); |
131 | 7 | cups_tree = proto_item_add_subtree(ti, ett_cups); |
132 | | |
133 | | /* Format (1450 bytes max.): */ |
134 | | /* type state uri ["location" ["info" ["make-and-model"]]]\n */ |
135 | | |
136 | 7 | ptype = get_hex_uint(tvb, offset, &next_offset); |
137 | 7 | len = next_offset - offset; |
138 | 7 | if (len != 0) { |
139 | 1 | ti = proto_tree_add_uint(cups_tree, hf_cups_ptype, tvb, offset, len, ptype); |
140 | 1 | ptype_subtree = proto_item_add_subtree(ti, ett_cups_ptype); |
141 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_default, tvb, offset, len, ENC_BIG_ENDIAN); |
142 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_implicit, tvb, offset, len, ENC_BIG_ENDIAN); |
143 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_variable, tvb, offset, len, ENC_BIG_ENDIAN); |
144 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_large, tvb, offset, len, ENC_BIG_ENDIAN); |
145 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_medium, tvb, offset, len, ENC_BIG_ENDIAN); |
146 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_small, tvb, offset, len, ENC_BIG_ENDIAN); |
147 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_sort, tvb, offset, len, ENC_BIG_ENDIAN); |
148 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_bind, tvb, offset, len, ENC_BIG_ENDIAN); |
149 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_cover, tvb, offset, len, ENC_BIG_ENDIAN); |
150 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_punch, tvb, offset, len, ENC_BIG_ENDIAN); |
151 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_collate, tvb, offset, len, ENC_BIG_ENDIAN); |
152 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_copies, tvb, offset, len, ENC_BIG_ENDIAN); |
153 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_staple, tvb, offset, len, ENC_BIG_ENDIAN); |
154 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_duplex, tvb, offset, len, ENC_BIG_ENDIAN); |
155 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_color, tvb, offset, len, ENC_BIG_ENDIAN); |
156 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_bw, tvb, offset, len, ENC_BIG_ENDIAN); |
157 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_remote, tvb, offset, len, ENC_BIG_ENDIAN); |
158 | 1 | proto_tree_add_item(ptype_subtree, hf_cups_ptype_class, tvb, offset, len, ENC_BIG_ENDIAN); |
159 | 1 | } |
160 | 7 | offset = next_offset; |
161 | | |
162 | 7 | if (!skip_space(tvb, offset, &next_offset)) |
163 | 1 | return offset; /* end of packet */ |
164 | 6 | offset = next_offset; |
165 | | |
166 | 6 | state = get_hex_uint(tvb, offset, &next_offset); |
167 | 6 | len = next_offset - offset; |
168 | 6 | if (len != 0) { |
169 | 0 | proto_tree_add_uint(cups_tree, hf_cups_state, tvb, offset, len, state); |
170 | 0 | } |
171 | 6 | offset = next_offset; |
172 | | |
173 | 6 | if (!skip_space(tvb, offset, &next_offset)) |
174 | 0 | return offset; /* end of packet */ |
175 | 6 | offset = next_offset; |
176 | | |
177 | 6 | str = get_unquoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
178 | 6 | if (str == NULL) |
179 | 3 | return offset; /* separator/terminator not found */ |
180 | | |
181 | 3 | proto_tree_add_string(cups_tree, hf_cups_uri, tvb, offset, len, str); |
182 | 3 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s (%s)", |
183 | 3 | str, val_to_str(state, cups_state_values, "0x%x")); |
184 | 3 | offset = next_offset; |
185 | | |
186 | 3 | if (!cups_tree) |
187 | 0 | return offset; |
188 | | |
189 | 3 | if (!skip_space(tvb, offset, &next_offset)) |
190 | 2 | return offset; /* end of packet */ |
191 | 1 | offset = next_offset; |
192 | | |
193 | 1 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
194 | 1 | if (str == NULL) |
195 | 1 | return offset; /* separator/terminator not found */ |
196 | 0 | proto_tree_add_string(cups_tree, hf_cups_location, tvb, offset+1, len, str); |
197 | 0 | offset = next_offset; |
198 | |
|
199 | 0 | if (!skip_space(tvb, offset, &next_offset)) |
200 | 0 | return offset; /* end of packet */ |
201 | 0 | offset = next_offset; |
202 | |
|
203 | 0 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
204 | 0 | if (str == NULL) |
205 | 0 | return offset; /* separator/terminator not found */ |
206 | 0 | proto_tree_add_string(cups_tree, hf_cups_information, tvb, offset+1, len, str); |
207 | 0 | offset = next_offset; |
208 | |
|
209 | 0 | if (!skip_space(tvb, offset, &next_offset)) |
210 | 0 | return offset; /* end of packet */ |
211 | 0 | offset = next_offset; |
212 | |
|
213 | 0 | str = get_quoted_string(pinfo->pool, tvb, offset, &next_offset, &len); |
214 | 0 | if (str == NULL) |
215 | 0 | return offset; /* separator/terminator not found */ |
216 | 0 | proto_tree_add_string(cups_tree, hf_cups_make_model, tvb, offset+1, len, str); |
217 | |
|
218 | 0 | return next_offset; |
219 | 0 | } |
220 | | |
221 | | static unsigned |
222 | | get_hex_uint(tvbuff_t *tvb, int offset, int *next_offset) |
223 | 13 | { |
224 | 13 | int c; |
225 | 13 | unsigned u = 0; |
226 | | |
227 | 13 | while (g_ascii_isxdigit(c = tvb_get_uint8(tvb, offset))) { |
228 | 1 | u = 16*u + ws_xton(c); |
229 | | |
230 | 1 | offset++; |
231 | 1 | } |
232 | | |
233 | 13 | *next_offset = offset; |
234 | | |
235 | 13 | return u; |
236 | 13 | } |
237 | | |
238 | | static bool |
239 | | skip_space(tvbuff_t *tvb, int offset, int *next_offset) |
240 | 16 | { |
241 | 16 | int c; |
242 | | |
243 | 17 | while ((c = tvb_get_uint8(tvb, offset)) == ' ') |
244 | 1 | offset++; |
245 | 16 | if (c == '\r' || c == '\n') |
246 | 3 | return false; /* end of packet */ |
247 | | |
248 | 13 | *next_offset = offset; |
249 | | |
250 | 13 | return true; |
251 | 16 | } |
252 | | |
253 | | static const uint8_t* |
254 | | get_quoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, int offset, int *next_offset, unsigned *len) |
255 | 1 | { |
256 | 1 | int c; |
257 | 1 | const uint8_t* s = NULL; |
258 | 1 | unsigned l = 0; |
259 | 1 | int o; |
260 | | |
261 | 1 | c = tvb_get_uint8(tvb, offset); |
262 | 1 | if (c == '"') { |
263 | 0 | o = tvb_find_uint8(tvb, offset+1, -1, '"'); |
264 | 0 | if (o != -1) { |
265 | 0 | offset++; |
266 | 0 | l = o - offset; |
267 | 0 | s = tvb_get_string_enc(scope, tvb, offset, l, ENC_UTF_8); |
268 | 0 | offset = o + 1; |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | 1 | *next_offset = offset; |
273 | 1 | *len = l; |
274 | | |
275 | 1 | return s; |
276 | 1 | } |
277 | | |
278 | | static const uint8_t* |
279 | | get_unquoted_string(wmem_allocator_t *scope, tvbuff_t *tvb, int offset, int *next_offset, unsigned *len) |
280 | 6 | { |
281 | 6 | const uint8_t* s = NULL; |
282 | 6 | unsigned l = 0; |
283 | 6 | int o; |
284 | | |
285 | 6 | o = tvb_ws_mempbrk_pattern_uint8(tvb, offset, -1, &pbrk_whitespace, NULL); |
286 | 6 | if (o != -1) { |
287 | 3 | l = o - offset; |
288 | 3 | s = tvb_get_string_enc(scope, tvb, offset, l, ENC_UTF_8); |
289 | 3 | offset = o; |
290 | 3 | } |
291 | | |
292 | 6 | *next_offset = offset; |
293 | 6 | *len = l; |
294 | | |
295 | 6 | return s; |
296 | 6 | } |
297 | | |
298 | | /**********************************************************************/ |
299 | | |
300 | | void |
301 | | proto_register_cups(void) |
302 | 14 | { |
303 | 14 | static hf_register_info hf[] = { |
304 | 14 | { &hf_cups_ptype, |
305 | 14 | { "Type", "cups.ptype", FT_UINT32, BASE_HEX, |
306 | 14 | NULL, 0x0, NULL, HFILL }}, |
307 | 14 | { &hf_cups_ptype_default, |
308 | 14 | { "Default printer on network", "cups.ptype.default", FT_BOOLEAN, 32, |
309 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_DEFAULT, NULL, HFILL }}, |
310 | 14 | { &hf_cups_ptype_implicit, |
311 | 14 | { "Class", "cups.ptype.implicit", FT_BOOLEAN, 32, |
312 | 14 | TFS(&tfs_implicit_explicit), CUPS_PRINTER_IMPLICIT, NULL, HFILL }}, |
313 | 14 | { &hf_cups_ptype_variable, |
314 | 14 | { "Can print variable sizes", "cups.ptype.variable", FT_BOOLEAN, 32, |
315 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_VARIABLE, NULL, HFILL }}, |
316 | 14 | { &hf_cups_ptype_large, |
317 | 14 | { "Can print up to 36x48 inches", "cups.ptype.large", FT_BOOLEAN, 32, |
318 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_LARGE, NULL, HFILL }}, |
319 | 14 | { &hf_cups_ptype_medium, |
320 | 14 | { "Can print up to 18x24 inches", "cups.ptype.medium", FT_BOOLEAN, 32, |
321 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_MEDIUM, NULL, HFILL }}, |
322 | 14 | { &hf_cups_ptype_small, |
323 | 14 | { "Can print up to 9x14 inches", "cups.ptype.small", FT_BOOLEAN, 32, |
324 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_SMALL, NULL, HFILL }}, |
325 | 14 | { &hf_cups_ptype_sort, |
326 | 14 | { "Can sort", "cups.ptype.sort", FT_BOOLEAN, 32, |
327 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_SORT, NULL, HFILL }}, |
328 | 14 | { &hf_cups_ptype_bind, |
329 | 14 | { "Can bind", "cups.ptype.bind", FT_BOOLEAN, 32, |
330 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_BIND, NULL, HFILL }}, |
331 | 14 | { &hf_cups_ptype_cover, |
332 | 14 | { "Can cover", "cups.ptype.cover", FT_BOOLEAN, 32, |
333 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_COVER, NULL, HFILL }}, |
334 | 14 | { &hf_cups_ptype_punch, |
335 | 14 | { "Can punch holes", "cups.ptype.punch", FT_BOOLEAN, 32, |
336 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_PUNCH, NULL, HFILL }}, |
337 | 14 | { &hf_cups_ptype_collate, |
338 | 14 | { "Can do fast collating", "cups.ptype.collate", FT_BOOLEAN, 32, |
339 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_COLLATE, NULL, HFILL }}, |
340 | 14 | { &hf_cups_ptype_copies, |
341 | 14 | { "Can do fast copies", "cups.ptype.copies", FT_BOOLEAN, 32, |
342 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_COPIES, NULL, HFILL }}, |
343 | 14 | { &hf_cups_ptype_staple, |
344 | 14 | { "Can staple", "cups.ptype.staple", FT_BOOLEAN, 32, |
345 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_STAPLE, NULL, HFILL }}, |
346 | 14 | { &hf_cups_ptype_duplex, |
347 | 14 | { "Can duplex", "cups.ptype.duplex", FT_BOOLEAN, 32, |
348 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_DUPLEX, NULL, HFILL }}, |
349 | 14 | { &hf_cups_ptype_color, |
350 | 14 | { "Can print color", "cups.ptype.color", FT_BOOLEAN, 32, |
351 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_COLOR, NULL, HFILL }}, |
352 | 14 | { &hf_cups_ptype_bw, |
353 | 14 | { "Can print black", "cups.ptype.bw", FT_BOOLEAN, 32, |
354 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_BW, NULL, HFILL }}, |
355 | 14 | { &hf_cups_ptype_remote, |
356 | 14 | { "Remote", "cups.ptype.remote", FT_BOOLEAN, 32, |
357 | 14 | TFS(&tfs_yes_no), CUPS_PRINTER_REMOTE, NULL, HFILL }}, |
358 | 14 | { &hf_cups_ptype_class, |
359 | 14 | { "Class", "cups.ptype.class", FT_BOOLEAN, 32, |
360 | 14 | TFS(&tfs_printer_class), CUPS_PRINTER_CLASS, NULL, HFILL }}, |
361 | 14 | { &hf_cups_state, |
362 | 14 | { "State", "cups.state", FT_UINT8, BASE_HEX, |
363 | 14 | VALS(cups_state_values), 0x0, NULL, HFILL }}, |
364 | 14 | { &hf_cups_uri, |
365 | 14 | { "URI", "cups.uri", FT_STRING, BASE_NONE, |
366 | 14 | NULL, 0x0, NULL, HFILL }}, |
367 | 14 | { &hf_cups_location, |
368 | 14 | { "Location", "cups.location", FT_STRING, BASE_NONE, |
369 | 14 | NULL, 0x0, NULL, HFILL }}, |
370 | 14 | { &hf_cups_information, |
371 | 14 | { "Information", "cups.information", FT_STRING, BASE_NONE, |
372 | 14 | NULL, 0x0, NULL, HFILL }}, |
373 | 14 | { &hf_cups_make_model, |
374 | 14 | { "Make and model", "cups.make_model", FT_STRING, BASE_NONE, |
375 | 14 | NULL, 0x0, NULL, HFILL }}, |
376 | 14 | }; |
377 | | |
378 | 14 | static int *ett[] = { |
379 | 14 | &ett_cups, |
380 | 14 | &ett_cups_ptype |
381 | 14 | }; |
382 | | |
383 | 14 | proto_cups = proto_register_protocol("Common Unix Printing System (CUPS) Browsing Protocol", "CUPS", "cups"); |
384 | 14 | cups_handle = register_dissector("cups", dissect_cups, proto_cups); |
385 | 14 | proto_register_field_array(proto_cups, hf, array_length(hf)); |
386 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
387 | | |
388 | | /* compile patterns */ |
389 | 14 | ws_mempbrk_compile(&pbrk_whitespace, " \t\r\n"); |
390 | 14 | } |
391 | | |
392 | | void |
393 | | proto_reg_handoff_cups(void) |
394 | 14 | { |
395 | 14 | dissector_add_uint_with_preference("udp.port", UDP_PORT_CUPS, cups_handle); |
396 | 14 | } |
397 | | |
398 | | /* |
399 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
400 | | * |
401 | | * Local variables: |
402 | | * c-basic-offset: 4 |
403 | | * tab-width: 8 |
404 | | * indent-tabs-mode: nil |
405 | | * End: |
406 | | * |
407 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
408 | | * :indentSize=4:tabSize=8:noTabs=true: |
409 | | */ |