/src/resiprocate/resip/stack/ParserCategory.cxx
Line | Count | Source |
1 | | #if defined(HAVE_CONFIG_H) |
2 | | #include "config.h" |
3 | | #endif |
4 | | |
5 | | #include "resip/stack/HeaderFieldValue.hxx" |
6 | | #include "resip/stack/ParserCategory.hxx" |
7 | | #include "rutil/ParseBuffer.hxx" |
8 | | #include "resip/stack/SipMessage.hxx" |
9 | | #include "rutil/DataStream.hxx" |
10 | | #include "rutil/ParseBuffer.hxx" |
11 | | #include "rutil/compat.hxx" |
12 | | |
13 | | #include "resip/stack/UnknownParameter.hxx" |
14 | | #include "resip/stack/ExtensionParameter.hxx" |
15 | | |
16 | | #include <iostream> |
17 | | #include "rutil/ResipAssert.h" |
18 | | |
19 | | #include "rutil/Logger.hxx" |
20 | | //#include "rutil/WinLeakCheck.hxx" // not compatible with placement new used below |
21 | | |
22 | | #define RESIPROCATE_SUBSYSTEM Subsystem::SIP |
23 | | |
24 | | using namespace resip; |
25 | | using namespace std; |
26 | | |
27 | | const ParserCategory::ParameterTypeSet |
28 | | ParserCategory::EmptyParameterTypeSet; |
29 | | |
30 | | ParserCategory::ParserCategory(const HeaderFieldValue& headerFieldValue, |
31 | | Headers::Type headerType, |
32 | | PoolBase* pool) |
33 | 72.5k | : LazyParser(headerFieldValue), |
34 | 72.5k | mParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
35 | 72.5k | mUnknownParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
36 | 72.5k | mPool(pool), |
37 | 72.5k | mHeaderType(headerType) |
38 | 72.5k | { |
39 | 72.5k | } |
40 | | |
41 | | ParserCategory::ParserCategory(const char* buf, |
42 | | int length, |
43 | | Headers::Type type, |
44 | | PoolBase* pool): |
45 | 0 | LazyParser(buf, length), |
46 | 0 | mParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
47 | 0 | mUnknownParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
48 | 0 | mPool(pool), |
49 | 0 | mHeaderType(type) |
50 | 0 | {} |
51 | | |
52 | | ParserCategory::ParserCategory(PoolBase* pool) |
53 | 30.7k | : LazyParser(), |
54 | 30.7k | mParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
55 | 30.7k | mUnknownParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
56 | 30.7k | mPool(pool), |
57 | 30.7k | mHeaderType(Headers::NONE) |
58 | 30.7k | { |
59 | 30.7k | } |
60 | | |
61 | | ParserCategory::ParserCategory(const ParserCategory& rhs, |
62 | | PoolBase* pool) |
63 | 72.4k | : LazyParser(rhs), |
64 | 72.4k | mParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
65 | 72.4k | mUnknownParameters(StlPoolAllocator<Parameter*, PoolBase>(pool)), |
66 | 72.4k | mPool(pool), |
67 | 72.4k | mHeaderType(rhs.mHeaderType) |
68 | 72.4k | { |
69 | 72.4k | if (isParsed()) |
70 | 72.4k | { |
71 | 72.4k | copyParametersFrom(rhs); |
72 | 72.4k | } |
73 | 72.4k | } |
74 | | |
75 | | ParserCategory& |
76 | | ParserCategory::operator=(const ParserCategory& rhs) |
77 | 5.53k | { |
78 | 5.53k | if (this != &rhs) |
79 | 5.53k | { |
80 | 5.53k | clear(); |
81 | 5.53k | mHeaderType = rhs.mHeaderType; |
82 | 5.53k | LazyParser::operator=(rhs); |
83 | 5.53k | if (rhs.isParsed()) |
84 | 5.53k | { |
85 | 5.53k | copyParametersFrom(rhs); |
86 | 5.53k | } |
87 | 5.53k | } |
88 | 5.53k | return *this; |
89 | 5.53k | } |
90 | | |
91 | | void |
92 | | ParserCategory::clear() |
93 | 181k | { |
94 | | //DebugLog(<<"ParserCategory::clear"); |
95 | 181k | LazyParser::clear(); |
96 | | |
97 | 189k | while(!mParameters.empty()) |
98 | 8.35k | { |
99 | 8.35k | freeParameter(mParameters.back()); |
100 | 8.35k | mParameters.pop_back(); |
101 | 8.35k | } |
102 | | |
103 | 232k | while(!mUnknownParameters.empty()) |
104 | 51.5k | { |
105 | 51.5k | freeParameter(mUnknownParameters.back()); |
106 | 51.5k | mUnknownParameters.pop_back(); |
107 | 51.5k | } |
108 | 181k | } |
109 | | |
110 | | void |
111 | | ParserCategory::copyParametersFrom(const ParserCategory& other) |
112 | 77.9k | { |
113 | 77.9k | mParameters.reserve(other.mParameters.size()); |
114 | 77.9k | mUnknownParameters.reserve(other.mUnknownParameters.size()); |
115 | | |
116 | 77.9k | for (ParameterList::const_iterator it = other.mParameters.begin(); |
117 | 78.5k | it != other.mParameters.end(); it++) |
118 | 654 | { |
119 | 654 | mParameters.push_back((*it)->clone()); |
120 | 654 | } |
121 | 77.9k | for (ParameterList::const_iterator it = other.mUnknownParameters.begin(); |
122 | 83.8k | it != other.mUnknownParameters.end(); it++) |
123 | 5.88k | { |
124 | 5.88k | mUnknownParameters.push_back((*it)->clone()); |
125 | 5.88k | } |
126 | 77.9k | } |
127 | | |
128 | | ParserCategory::~ParserCategory() |
129 | 175k | { |
130 | 175k | clear(); |
131 | 175k | } |
132 | | |
133 | | const Data& |
134 | | ParserCategory::param(const ExtensionParameter& param) const |
135 | 0 | { |
136 | 0 | checkParsed(); |
137 | 0 | Parameter* p = getParameterByData(param.getName()); |
138 | 0 | if (!p) |
139 | 0 | { |
140 | 0 | InfoLog(<< "Referenced an unknown parameter " << param.getName()); |
141 | 0 | throw Exception("Missing unknown parameter", __FILE__, __LINE__); |
142 | 0 | } |
143 | 0 | return static_cast<UnknownParameter*>(p)->value(); |
144 | 0 | } |
145 | | |
146 | | Data& |
147 | | ParserCategory::param(const ExtensionParameter& param) |
148 | 0 | { |
149 | 0 | checkParsed(); |
150 | 0 | Parameter* p = getParameterByData(param.getName()); |
151 | 0 | if (!p) |
152 | 0 | { |
153 | 0 | p = new UnknownParameter(param.getName()); |
154 | 0 | mUnknownParameters.push_back(p); |
155 | 0 | } |
156 | 0 | return static_cast<UnknownParameter*>(p)->value(); |
157 | 0 | } |
158 | | |
159 | | // removing non-present parameter is allowed |
160 | | void |
161 | | ParserCategory::remove(const ParamBase& paramType) |
162 | 0 | { |
163 | 0 | checkParsed(); |
164 | 0 | removeParameterByEnum(paramType.getTypeNum()); |
165 | 0 | } |
166 | | |
167 | | void |
168 | | ParserCategory::remove(const ExtensionParameter& param) |
169 | 0 | { |
170 | 0 | checkParsed(); |
171 | 0 | removeParameterByData(param.getName()); |
172 | 0 | } |
173 | | |
174 | | bool |
175 | | ParserCategory::exists(const ExtensionParameter& param) const |
176 | 0 | { |
177 | 0 | checkParsed(); |
178 | 0 | return getParameterByData(param.getName()) != NULL; |
179 | 0 | } |
180 | | |
181 | | void |
182 | | ParserCategory::removeParametersExcept(const ParameterTypeSet& set) |
183 | 0 | { |
184 | 0 | checkParsed(); |
185 | 0 | for (ParameterList::iterator it = mParameters.begin(); |
186 | 0 | it != mParameters.end();) |
187 | 0 | { |
188 | 0 | if (set.find((*it)->getType()) == set.end()) |
189 | 0 | { |
190 | 0 | freeParameter(*it); |
191 | 0 | it = mParameters.erase(it); |
192 | 0 | } |
193 | 0 | else |
194 | 0 | { |
195 | 0 | ++it; |
196 | 0 | } |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | void |
201 | | ParserCategory::clearUnknownParameters() |
202 | 631 | { |
203 | 631 | for (ParameterList::iterator it = mUnknownParameters.begin(); |
204 | 5.17k | it != mUnknownParameters.end(); it++) |
205 | 4.54k | { |
206 | 4.54k | freeParameter(*it); |
207 | 4.54k | } |
208 | 631 | mUnknownParameters.clear(); |
209 | 631 | } |
210 | | |
211 | | void |
212 | | ParserCategory::parseParameters(ParseBuffer& pb) |
213 | 23.8k | { |
214 | 1.94M | while (!pb.eof() ) |
215 | 1.93M | { |
216 | 1.93M | const char* start = pb.position(); |
217 | 1.93M | pb.skipWhitespace(); |
218 | | |
219 | 1.93M | if ( (!pb.eof() && *pb.position() == Symbols::SEMI_COLON[0]) ) |
220 | 1.92M | { |
221 | | // extract the key |
222 | 1.92M | pb.skipChar(); |
223 | 1.92M | const char* keyStart = pb.skipWhitespace(); |
224 | 1.92M | static std::bitset<256> terminators1=Data::toBitset(" \t\r\n;=?>"); //!dlb! @ here? |
225 | 1.92M | const char* keyEnd = pb.skipToOneOf(terminators1); |
226 | | |
227 | 1.92M | if((int)(keyEnd-keyStart) != 0) |
228 | 1.92M | { |
229 | 1.92M | ParameterTypes::Type type = ParameterTypes::getType(keyStart, (unsigned int)(keyEnd - keyStart)); |
230 | 1.92M | static std::bitset<256> terminators2 = Data::toBitset(" \t\r\n;?>"); |
231 | 1.92M | Parameter* p; |
232 | 1.92M | if (type == ParameterTypes::UNKNOWN || |
233 | 100k | !(p=createParam(type, pb, terminators2,getPool()))) |
234 | 1.88M | { |
235 | 1.88M | UnknownParameter* unknownParam = new (getPool()) UnknownParameter(keyStart, |
236 | 1.88M | int((keyEnd - keyStart)), |
237 | 1.88M | pb, |
238 | 1.88M | terminators2); |
239 | | |
240 | 1.88M | if(!addParameter(unknownParam)) |
241 | 1.84M | { |
242 | 1.84M | freeParameter(unknownParam); |
243 | 1.84M | } |
244 | 1.88M | } |
245 | 31.8k | else |
246 | 31.8k | { |
247 | 31.8k | if(!addParameter(p)) |
248 | 29.7k | { |
249 | 29.7k | freeParameter(p); |
250 | 29.7k | } |
251 | 31.8k | } |
252 | 1.92M | } |
253 | 1.92M | } |
254 | 8.30k | else |
255 | 8.30k | { |
256 | 8.30k | pb.reset(start); |
257 | 8.30k | return; |
258 | 8.30k | } |
259 | 1.93M | } |
260 | 23.8k | } |
261 | | |
262 | | bool |
263 | | ParserCategory::addParameter(Parameter* param) |
264 | 32.1k | { |
265 | 32.1k | resip_assert(param); |
266 | | |
267 | 32.1k | if (getParameterByEnum(param->getType()) == nullptr) |
268 | 1.67k | { |
269 | | // invoke the particular factory |
270 | 1.67k | mParameters.push_back(param); |
271 | 1.67k | return true; |
272 | 1.67k | } |
273 | 30.4k | else |
274 | 30.4k | { |
275 | | // RFC 3261 7.3.1 & 19.1.1 |
276 | | // any given parameter-name MUST NOT appear more than once |
277 | 30.4k | WarningLog(<< "Duplicate parameter-name \"" << param->getName() << "\", skip it."); |
278 | 30.4k | return false; |
279 | 30.4k | } |
280 | 32.1k | } |
281 | | |
282 | | bool |
283 | | ParserCategory::addParameter(UnknownParameter* unknownParam) |
284 | 1.89M | { |
285 | 1.89M | resip_assert(unknownParam); |
286 | | |
287 | 1.89M | const Data& unknownParamName = unknownParam->getName(); |
288 | | |
289 | 1.89M | if (getParameterByData(unknownParamName) == nullptr) |
290 | 50.2k | { |
291 | 50.2k | mUnknownParameters.push_back(unknownParam); |
292 | 50.2k | return true; |
293 | 50.2k | } |
294 | 1.84M | else |
295 | 1.84M | { |
296 | | // RFC 3261 7.3.1 & 19.1.1 |
297 | | // any given parameter-name MUST NOT appear more than once |
298 | 1.84M | WarningLog(<< "Duplicate parameter-name \"" << unknownParamName << "\", skip it."); |
299 | 1.84M | return false; |
300 | 1.84M | } |
301 | 1.89M | } |
302 | | |
303 | | Parameter* |
304 | | ParserCategory::createParam(ParameterTypes::Type type, ParseBuffer& pb, const std::bitset<256>& terminators, PoolBase* pool) |
305 | 12.3k | { |
306 | 12.3k | return 0; |
307 | 12.3k | } |
308 | | |
309 | | static Data up_Msgr("msgr"); |
310 | | |
311 | | EncodeStream& |
312 | | ParserCategory::encodeParameters(EncodeStream& str) const |
313 | 0 | { |
314 | | |
315 | 0 | for (ParameterList::const_iterator it = mParameters.begin(); |
316 | 0 | it != mParameters.end(); it++) |
317 | 0 | { |
318 | | #if 0 |
319 | | // !cj! - may be wrong just hacking |
320 | | // The goal of all this is not to add a tag if the tag is empty |
321 | | ParameterTypes::Type type = (*it)->getType(); |
322 | | |
323 | | if ( type == ParameterTypes::tag ) |
324 | | { |
325 | | Parameter* p = (*it); |
326 | | DataParameter* d = dynamic_cast<DataParameter*>(p); |
327 | | |
328 | | Data& data = d->value(); |
329 | | |
330 | | if ( !data.empty() ) |
331 | | { |
332 | | str << Symbols::SEMI_COLON; |
333 | | // !ah! this is a TOTAL hack to work around an MSN bug that |
334 | | // !ah! requires a SPACE after the SEMI following the MIME type. |
335 | | if (it == mParameters.begin() && getParameterByData(up_Msgr)) |
336 | | { |
337 | | str << Symbols::SPACE; |
338 | | } |
339 | | |
340 | | (*it)->encode(str); |
341 | | } |
342 | | } |
343 | | else |
344 | | { |
345 | | str << Symbols::SEMI_COLON; |
346 | | // !ah! this is a TOTAL hack to work around an MSN bug that |
347 | | // !ah! requires a SPACE after the SEMI following the MIME type. |
348 | | if (it == mParameters.begin() && getParameterByData(up_Msgr)) |
349 | | { |
350 | | str << Symbols::SPACE; |
351 | | } |
352 | | |
353 | | (*it)->encode(str); |
354 | | } |
355 | | |
356 | | #else |
357 | 0 | str << Symbols::SEMI_COLON; |
358 | | // !ah! this is a TOTAL hack to work around an MSN bug that |
359 | | // !ah! requires a SPACE after the SEMI following the MIME type. |
360 | 0 | if (it == mParameters.begin() && getParameterByData(up_Msgr)) |
361 | 0 | { |
362 | 0 | str << Symbols::SPACE; |
363 | 0 | } |
364 | | |
365 | 0 | (*it)->encode(str); |
366 | 0 | #endif |
367 | 0 | } |
368 | 0 | for (ParameterList::const_iterator it = mUnknownParameters.begin(); |
369 | 0 | it != mUnknownParameters.end(); it++) |
370 | 0 | { |
371 | 0 | str << Symbols::SEMI_COLON; |
372 | 0 | (*it)->encode(str); |
373 | 0 | } |
374 | 0 | return str; |
375 | 0 | } |
376 | | |
377 | | EncodeStream& |
378 | | resip::operator<<(EncodeStream& stream, const ParserCategory& category) |
379 | 0 | { |
380 | 0 | category.checkParsed(); |
381 | 0 | return category.encode(stream); |
382 | 0 | } |
383 | | |
384 | | Parameter* |
385 | | ParserCategory::getParameterByEnum(ParameterTypes::Type type) const |
386 | 50.2k | { |
387 | 50.2k | for (ParameterList::const_iterator it = mParameters.begin(); |
388 | 51.1k | it != mParameters.end(); it++) |
389 | 37.3k | { |
390 | 37.3k | if ((*it)->getType() == type) |
391 | 36.5k | { |
392 | 36.5k | return *it; |
393 | 36.5k | } |
394 | 37.3k | } |
395 | 13.7k | return 0; |
396 | 50.2k | } |
397 | | |
398 | | void |
399 | | ParserCategory::setParameter(const Parameter* parameter) |
400 | 0 | { |
401 | 0 | resip_assert(parameter); |
402 | |
|
403 | 0 | for (ParameterList::iterator it = mParameters.begin(); |
404 | 0 | it != mParameters.end(); it++) |
405 | 0 | { |
406 | 0 | if ((*it)->getType() == parameter->getType()) |
407 | 0 | { |
408 | 0 | freeParameter(*it); |
409 | 0 | mParameters.erase(it); |
410 | 0 | mParameters.push_back(parameter->clone()); |
411 | 0 | return; |
412 | 0 | } |
413 | 0 | } |
414 | | |
415 | | // !dlb! kinda hacky -- what is the correct semantics here? |
416 | | // should be quietly add, quietly do nothing, throw? |
417 | 0 | mParameters.push_back(parameter->clone()); |
418 | 0 | } |
419 | | |
420 | | void |
421 | | ParserCategory::removeParameterByEnum(ParameterTypes::Type type) |
422 | 0 | { |
423 | | // remove all instances |
424 | 0 | for (ParameterList::iterator it = mParameters.begin(); |
425 | 0 | it != mParameters.end();) |
426 | 0 | { |
427 | 0 | if ((*it)->getType() == type) |
428 | 0 | { |
429 | 0 | freeParameter(*it); |
430 | 0 | it = mParameters.erase(it); |
431 | 0 | } |
432 | 0 | else |
433 | 0 | { |
434 | 0 | ++it; |
435 | 0 | } |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | | Parameter* |
440 | | ParserCategory::getParameterByData(const Data& data) const |
441 | 1.89M | { |
442 | 1.89M | for (ParameterList::const_iterator it = mUnknownParameters.begin(); |
443 | 5.87M | it != mUnknownParameters.end(); it++) |
444 | 5.82M | { |
445 | 5.82M | if (isEqualNoCase((*it)->getName(), data)) |
446 | 1.84M | { |
447 | 1.84M | return *it; |
448 | 1.84M | } |
449 | 5.82M | } |
450 | 50.2k | return 0; |
451 | 1.89M | } |
452 | | |
453 | | void |
454 | | ParserCategory::removeParameterByData(const Data& data) |
455 | 0 | { |
456 | | // remove all instances |
457 | 0 | for (ParameterList::iterator it = mUnknownParameters.begin(); |
458 | 0 | it != mUnknownParameters.end();) |
459 | 0 | { |
460 | 0 | if ((*it)->getName() == data) |
461 | 0 | { |
462 | 0 | freeParameter(*it); |
463 | 0 | it = mUnknownParameters.erase(it); |
464 | 0 | } |
465 | 0 | else |
466 | 0 | { |
467 | 0 | ++it; |
468 | 0 | } |
469 | 0 | } |
470 | 0 | } |
471 | | |
472 | | Data |
473 | | ParserCategory::commutativeParameterHash() const |
474 | 0 | { |
475 | 0 | Data buffer; |
476 | 0 | Data working; |
477 | |
|
478 | 0 | for (ParameterList::const_iterator i = mParameters.begin(); i != mParameters.end(); ++i) |
479 | 0 | { |
480 | 0 | if ((*i)->getType() != ParameterTypes::lr) |
481 | 0 | { |
482 | 0 | buffer.clear(); |
483 | 0 | { |
484 | 0 | DataStream strm(buffer); |
485 | 0 | (*i)->encode(strm); |
486 | 0 | } |
487 | 0 | working ^= buffer; |
488 | 0 | } |
489 | 0 | } |
490 | |
|
491 | 0 | buffer.clear(); |
492 | 0 | for (ParameterList::const_iterator i = mUnknownParameters.begin(); i != mUnknownParameters.end(); ++i) |
493 | 0 | { |
494 | 0 | UnknownParameter* p = static_cast<UnknownParameter*>(*i); |
495 | 0 | buffer = p->getName(); |
496 | 0 | buffer += p->value(); |
497 | 0 | working ^= buffer; |
498 | 0 | } |
499 | | |
500 | 0 | return working; |
501 | 0 | } |
502 | | |
503 | | const Data& |
504 | | ParserCategory::errorContext() const |
505 | 72.5k | { |
506 | 72.5k | return Headers::getHeaderName(mHeaderType); |
507 | 72.5k | } |
508 | | |
509 | | /* ==================================================================== |
510 | | * The Vovida Software License, Version 1.0 |
511 | | * |
512 | | * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. |
513 | | * |
514 | | * Redistribution and use in source and binary forms, with or without |
515 | | * modification, are permitted provided that the following conditions |
516 | | * are met: |
517 | | * |
518 | | * 1. Redistributions of source code must retain the above copyright |
519 | | * notice, this list of conditions and the following disclaimer. |
520 | | * |
521 | | * 2. Redistributions in binary form must reproduce the above copyright |
522 | | * notice, this list of conditions and the following disclaimer in |
523 | | * the documentation and/or other materials provided with the |
524 | | * distribution. |
525 | | * |
526 | | * 3. The names "VOCAL", "Vovida Open Communication Application Library", |
527 | | * and "Vovida Open Communication Application Library (VOCAL)" must |
528 | | * not be used to endorse or promote products derived from this |
529 | | * software without prior written permission. For written |
530 | | * permission, please contact vocal@vovida.org. |
531 | | * |
532 | | * 4. Products derived from this software may not be called "VOCAL", nor |
533 | | * may "VOCAL" appear in their name, without prior written |
534 | | * permission of Vovida Networks, Inc. |
535 | | * |
536 | | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED |
537 | | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
538 | | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND |
539 | | * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA |
540 | | * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES |
541 | | * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, |
542 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
543 | | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
544 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
545 | | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
546 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
547 | | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
548 | | * DAMAGE. |
549 | | * |
550 | | * ==================================================================== |
551 | | * |
552 | | * This software consists of voluntary contributions made by Vovida |
553 | | * Networks, Inc. and many individuals on behalf of Vovida Networks, |
554 | | * Inc. For more information on Vovida Networks, Inc., please see |
555 | | * <http://www.vovida.org/>. |
556 | | * |
557 | | */ |
558 | | |
559 | | /* Local Variables: */ |
560 | | /* c-file-style: "ellemtel" */ |
561 | | /* End: */ |