Annotation of embedaddon/php/ext/dom/xpath.c, revision 1.1.1.1
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
5: | Copyright (c) 1997-2012 The PHP Group |
6: +----------------------------------------------------------------------+
7: | This source file is subject to version 3.01 of the PHP license, |
8: | that is bundled with this package in the file LICENSE, and is |
9: | available through the world-wide-web at the following url: |
10: | http://www.php.net/license/3_01.txt |
11: | If you did not receive a copy of the PHP license and are unable to |
12: | obtain it through the world-wide-web, please send a note to |
13: | license@php.net so we can mail you a copy immediately. |
14: +----------------------------------------------------------------------+
15: | Authors: Christian Stocker <chregu@php.net> |
16: | Rob Richards <rrichards@php.net> |
17: +----------------------------------------------------------------------+
18: */
19:
20: /* $Id: xpath.c 321634 2012-01-01 13:15:04Z felipe $ */
21:
22: #ifdef HAVE_CONFIG_H
23: #include "config.h"
24: #endif
25:
26: #include "php.h"
27: #if HAVE_LIBXML && HAVE_DOM
28: #include "php_dom.h"
29:
30: #define PHP_DOM_XPATH_QUERY 0
31: #define PHP_DOM_XPATH_EVALUATE 1
32:
33: /*
34: * class DOMXPath
35: */
36:
37: #if defined(LIBXML_XPATH_ENABLED)
38:
39: /* {{{ arginfo */
40: ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_xpath_construct, 0, 0, 1)
41: ZEND_ARG_OBJ_INFO(0, doc, DOMDocument, 0)
42: ZEND_END_ARG_INFO();
43:
44: ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_xpath_register_ns, 0, 0, 2)
45: ZEND_ARG_INFO(0, prefix)
46: ZEND_ARG_INFO(0, uri)
47: ZEND_END_ARG_INFO();
48:
49: ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_xpath_query, 0, 0, 1)
50: ZEND_ARG_INFO(0, expr)
51: ZEND_ARG_OBJ_INFO(0, context, DOMNode, 1)
52: ZEND_ARG_INFO(0, registerNodeNS)
53: ZEND_END_ARG_INFO();
54:
55: ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_xpath_evaluate, 0, 0, 1)
56: ZEND_ARG_INFO(0, expr)
57: ZEND_ARG_OBJ_INFO(0, context, DOMNode, 1)
58: ZEND_ARG_INFO(0, registerNodeNS)
59: ZEND_END_ARG_INFO();
60:
61: ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_xpath_register_php_functions, 0, 0, 0)
62: ZEND_END_ARG_INFO();
63: /* }}} */
64:
65: const zend_function_entry php_dom_xpath_class_functions[] = {
66: PHP_ME(domxpath, __construct, arginfo_dom_xpath_construct, ZEND_ACC_PUBLIC)
67: PHP_FALIAS(registerNamespace, dom_xpath_register_ns, arginfo_dom_xpath_register_ns)
68: PHP_FALIAS(query, dom_xpath_query, arginfo_dom_xpath_query)
69: PHP_FALIAS(evaluate, dom_xpath_evaluate, arginfo_dom_xpath_evaluate)
70: PHP_FALIAS(registerPhpFunctions, dom_xpath_register_php_functions, arginfo_dom_xpath_register_php_functions)
71: PHP_FE_END
72: };
73:
74:
75: static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int type) /* {{{ */
76: {
77: zval **args;
78: zval *retval;
79: int result, i, ret;
80: int error = 0;
81: zend_fcall_info fci;
82: zval handler;
83: xmlXPathObjectPtr obj;
84: char *str;
85: char *callable = NULL;
86: dom_xpath_object *intern;
87:
88: TSRMLS_FETCH();
89:
90: if (! zend_is_executing(TSRMLS_C)) {
91: xmlGenericError(xmlGenericErrorContext,
92: "xmlExtFunctionTest: Function called from outside of PHP\n");
93: error = 1;
94: } else {
95: intern = (dom_xpath_object *) ctxt->context->userData;
96: if (intern == NULL) {
97: xmlGenericError(xmlGenericErrorContext,
98: "xmlExtFunctionTest: failed to get the internal object\n");
99: error = 1;
100: }
101: else if (intern->registerPhpFunctions == 0) {
102: xmlGenericError(xmlGenericErrorContext,
103: "xmlExtFunctionTest: PHP Object did not register PHP functions\n");
104: error = 1;
105: }
106: }
107:
108: if (error == 1) {
109: for (i = nargs - 1; i >= 0; i--) {
110: obj = valuePop(ctxt);
111: xmlXPathFreeObject(obj);
112: }
113: return;
114: }
115:
116: fci.param_count = nargs - 1;
117: if (fci.param_count > 0) {
118: fci.params = safe_emalloc(fci.param_count, sizeof(zval**), 0);
119: args = safe_emalloc(fci.param_count, sizeof(zval *), 0);
120: }
121: /* Reverse order to pop values off ctxt stack */
122: for (i = nargs - 2; i >= 0; i--) {
123: obj = valuePop(ctxt);
124: MAKE_STD_ZVAL(args[i]);
125: switch (obj->type) {
126: case XPATH_STRING:
127: ZVAL_STRING(args[i], (char *)obj->stringval, 1);
128: break;
129: case XPATH_BOOLEAN:
130: ZVAL_BOOL(args[i], obj->boolval);
131: break;
132: case XPATH_NUMBER:
133: ZVAL_DOUBLE(args[i], obj->floatval);
134: break;
135: case XPATH_NODESET:
136: if (type == 1) {
137: str = (char *)xmlXPathCastToString(obj);
138: ZVAL_STRING(args[i], str, 1);
139: xmlFree(str);
140: } else if (type == 2) {
141: int j;
142: array_init(args[i]);
143: if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
144: for (j = 0; j < obj->nodesetval->nodeNr; j++) {
145: xmlNodePtr node = obj->nodesetval->nodeTab[j];
146: zval *child;
147: MAKE_STD_ZVAL(child);
148: /* not sure, if we need this... it's copied from xpath.c */
149: if (node->type == XML_NAMESPACE_DECL) {
150: xmlNsPtr curns;
151: xmlNodePtr nsparent;
152:
153: nsparent = node->_private;
154: curns = xmlNewNs(NULL, node->name, NULL);
155: if (node->children) {
156: curns->prefix = xmlStrdup((xmlChar *) node->children);
157: }
158: if (node->children) {
159: node = xmlNewDocNode(node->doc, NULL, (xmlChar *) node->children, node->name);
160: } else {
161: node = xmlNewDocNode(node->doc, NULL, (xmlChar *) "xmlns", node->name);
162: }
163: node->type = XML_NAMESPACE_DECL;
164: node->parent = nsparent;
165: node->ns = curns;
166: }
167: child = php_dom_create_object(node, &ret, NULL, child, (dom_object *)intern TSRMLS_CC);
168: add_next_index_zval(args[i], child);
169: }
170: }
171: }
172: break;
173: default:
174: ZVAL_STRING(args[i], (char *)xmlXPathCastToString(obj), 1);
175: }
176: xmlXPathFreeObject(obj);
177: fci.params[i] = &args[i];
178: }
179:
180: fci.size = sizeof(fci);
181: fci.function_table = EG(function_table);
182:
183: obj = valuePop(ctxt);
184: if (obj->stringval == NULL) {
185: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Handler name must be a string");
186: xmlXPathFreeObject(obj);
187: if (fci.param_count > 0) {
188: for (i = 0; i < nargs - 1; i++) {
189: zval_ptr_dtor(&args[i]);
190: }
191: efree(args);
192: efree(fci.params);
193: }
194: return;
195: }
196: INIT_PZVAL(&handler);
197: ZVAL_STRING(&handler, obj->stringval, 1);
198: xmlXPathFreeObject(obj);
199:
200: fci.function_name = &handler;
201: fci.symbol_table = NULL;
202: fci.object_ptr = NULL;
203: fci.retval_ptr_ptr = &retval;
204: fci.no_separation = 0;
205:
206: if (!zend_make_callable(&handler, &callable TSRMLS_CC)) {
207: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call handler %s()", callable);
208:
209: } else if ( intern->registerPhpFunctions == 2 && zend_hash_exists(intern->registered_phpfunctions, callable, strlen(callable) + 1) == 0) {
210: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Not allowed to call handler '%s()'.", callable);
211: /* Push an empty string, so that we at least have an xslt result... */
212: valuePush(ctxt, xmlXPathNewString((xmlChar *)""));
213: } else {
214: result = zend_call_function(&fci, NULL TSRMLS_CC);
215: if (result == FAILURE) {
216: if (Z_TYPE(handler) == IS_STRING) {
217: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to call handler %s()", Z_STRVAL_P(&handler));
218: }
219: /* retval is == NULL, when an exception occured, don't report anything, because PHP itself will handle that */
220: } else if (retval == NULL) {
221: } else {
222: if (retval->type == IS_OBJECT && instanceof_function( Z_OBJCE_P(retval), dom_node_class_entry TSRMLS_CC)) {
223: xmlNode *nodep;
224: dom_object *obj;
225: if (intern->node_list == NULL) {
226: ALLOC_HASHTABLE(intern->node_list);
227: zend_hash_init(intern->node_list, 0, NULL, ZVAL_PTR_DTOR, 0);
228: }
229: zval_add_ref(&retval);
230: zend_hash_next_index_insert(intern->node_list, &retval, sizeof(zval *), NULL);
231: obj = (dom_object *)zend_object_store_get_object(retval TSRMLS_CC);
232: nodep = dom_object_get_node(obj);
233: valuePush(ctxt, xmlXPathNewNodeSet(nodep));
234: } else if (retval->type == IS_BOOL) {
235: valuePush(ctxt, xmlXPathNewBoolean(retval->value.lval));
236: } else if (retval->type == IS_OBJECT) {
237: php_error_docref(NULL TSRMLS_CC, E_WARNING, "A PHP Object cannot be converted to a XPath-string");
238: valuePush(ctxt, xmlXPathNewString((xmlChar *)""));
239: } else {
240: convert_to_string_ex(&retval);
241: valuePush(ctxt, xmlXPathNewString( Z_STRVAL_P(retval)));
242: }
243: zval_ptr_dtor(&retval);
244: }
245: }
246: efree(callable);
247: zval_dtor(&handler);
248: if (fci.param_count > 0) {
249: for (i = 0; i < nargs - 1; i++) {
250: zval_ptr_dtor(&args[i]);
251: }
252: efree(args);
253: efree(fci.params);
254: }
255: }
256: /* }}} */
257:
258: static void dom_xpath_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
259: {
260: dom_xpath_ext_function_php(ctxt, nargs, 1);
261: }
262: /* }}} */
263:
264: static void dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
265: {
266: dom_xpath_ext_function_php(ctxt, nargs, 2);
267: }
268: /* }}} */
269:
270: /* {{{ proto void DOMXPath::__construct(DOMDocument doc) U */
271: PHP_METHOD(domxpath, __construct)
272: {
273: zval *id, *doc;
274: xmlDocPtr docp = NULL;
275: dom_object *docobj;
276: dom_xpath_object *intern;
277: xmlXPathContextPtr ctx, oldctx;
278: zend_error_handling error_handling;
279:
280: zend_replace_error_handling(EH_THROW, dom_domexception_class_entry, &error_handling TSRMLS_CC);
281: if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "OO", &id, dom_xpath_class_entry, &doc, dom_document_class_entry) == FAILURE) {
282: zend_restore_error_handling(&error_handling TSRMLS_CC);
283: return;
284: }
285:
286: zend_restore_error_handling(&error_handling TSRMLS_CC);
287: DOM_GET_OBJ(docp, doc, xmlDocPtr, docobj);
288:
289: ctx = xmlXPathNewContext(docp);
290: if (ctx == NULL) {
291: php_dom_throw_error(INVALID_STATE_ERR, 1 TSRMLS_CC);
292: RETURN_FALSE;
293: }
294:
295: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
296: if (intern != NULL) {
297: oldctx = (xmlXPathContextPtr)intern->ptr;
298: if (oldctx != NULL) {
299: php_libxml_decrement_doc_ref((php_libxml_node_object *)intern TSRMLS_CC);
300: xmlXPathFreeContext(oldctx);
301: }
302:
303: xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "functionString",
304: (const xmlChar *) "http://php.net/xpath",
305: dom_xpath_ext_function_string_php);
306: xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "function",
307: (const xmlChar *) "http://php.net/xpath",
308: dom_xpath_ext_function_object_php);
309:
310: intern->ptr = ctx;
311: ctx->userData = (void *)intern;
312: intern->document = docobj->document;
313: php_libxml_increment_doc_ref((php_libxml_node_object *)intern, docp TSRMLS_CC);
314: }
315: }
316: /* }}} end DOMXPath::__construct */
317:
318: /* {{{ document DOMDocument*/
319: int dom_xpath_document_read(dom_object *obj, zval **retval TSRMLS_DC)
320: {
321: xmlDoc *docp = NULL;
322: xmlXPathContextPtr ctx;
323: int ret;
324:
325: ctx = (xmlXPathContextPtr) obj->ptr;
326:
327: if (ctx) {
328: docp = (xmlDocPtr) ctx->doc;
329: }
330:
331: ALLOC_ZVAL(*retval);
332: if (NULL == (*retval = php_dom_create_object((xmlNodePtr) docp, &ret, NULL, *retval, obj TSRMLS_CC))) {
333: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot create required DOM object");
334: return FAILURE;
335: }
336: return SUCCESS;
337: }
338: /* }}} */
339:
340: /* {{{ proto boolean dom_xpath_register_ns(string prefix, string uri); */
341: PHP_FUNCTION(dom_xpath_register_ns)
342: {
343: zval *id;
344: xmlXPathContextPtr ctxp;
345: int prefix_len, ns_uri_len;
346: dom_xpath_object *intern;
347: unsigned char *prefix, *ns_uri;
348:
349: if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", &id, dom_xpath_class_entry, &prefix, &prefix_len, &ns_uri, &ns_uri_len) == FAILURE) {
350: return;
351: }
352:
353: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
354:
355: ctxp = (xmlXPathContextPtr) intern->ptr;
356: if (ctxp == NULL) {
357: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid XPath Context");
358: RETURN_FALSE;
359: }
360:
361: if (xmlXPathRegisterNs(ctxp, prefix, ns_uri) != 0) {
362: RETURN_FALSE
363: }
364: RETURN_TRUE;
365: }
366: /* }}} */
367:
368: static void dom_xpath_iter(zval *baseobj, dom_object *intern) /* {{{ */
369: {
370: dom_nnodemap_object *mapptr;
371:
372: mapptr = (dom_nnodemap_object *)intern->ptr;
373: mapptr->baseobjptr = baseobj;
374: mapptr->nodetype = DOM_NODESET;
375:
376: }
377: /* }}} */
378:
379: static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */
380: {
381: zval *id, *retval, *context = NULL;
382: xmlXPathContextPtr ctxp;
383: xmlNodePtr nodep = NULL;
384: xmlXPathObjectPtr xpathobjp;
385: int expr_len, ret, nsnbr = 0, xpath_type;
386: dom_xpath_object *intern;
387: dom_object *nodeobj;
388: char *expr;
389: xmlDoc *docp = NULL;
390: xmlNsPtr *ns = NULL;
391: zend_bool register_node_ns = 1;
392:
393: if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|O!b", &id, dom_xpath_class_entry, &expr, &expr_len, &context, dom_node_class_entry, ®ister_node_ns) == FAILURE) {
394: return;
395: }
396:
397: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
398:
399: ctxp = (xmlXPathContextPtr) intern->ptr;
400: if (ctxp == NULL) {
401: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid XPath Context");
402: RETURN_FALSE;
403: }
404:
405: docp = (xmlDocPtr) ctxp->doc;
406: if (docp == NULL) {
407: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid XPath Document Pointer");
408: RETURN_FALSE;
409: }
410:
411: if (context != NULL) {
412: DOM_GET_OBJ(nodep, context, xmlNodePtr, nodeobj);
413: }
414:
415: if (!nodep) {
416: nodep = xmlDocGetRootElement(docp);
417: }
418:
419: if (nodep && docp != nodep->doc) {
420: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Node From Wrong Document");
421: RETURN_FALSE;
422: }
423:
424: ctxp->node = nodep;
425:
426: if (register_node_ns) {
427: /* Register namespaces in the node */
428: ns = xmlGetNsList(docp, nodep);
429:
430: if (ns != NULL) {
431: while (ns[nsnbr] != NULL)
432: nsnbr++;
433: }
434: }
435:
436:
437: ctxp->namespaces = ns;
438: ctxp->nsNr = nsnbr;
439:
440: xpathobjp = xmlXPathEvalExpression(expr, ctxp);
441: ctxp->node = NULL;
442:
443: if (ns != NULL) {
444: xmlFree(ns);
445: ctxp->namespaces = NULL;
446: ctxp->nsNr = 0;
447: }
448:
449: if (! xpathobjp) {
450: RETURN_FALSE;
451: }
452:
453: if (type == PHP_DOM_XPATH_QUERY) {
454: xpath_type = XPATH_NODESET;
455: } else {
456: xpath_type = xpathobjp->type;
457: }
458:
459: switch (xpath_type) {
460:
461: case XPATH_NODESET:
462: {
463: int i;
464: xmlNodeSetPtr nodesetp;
465:
466: MAKE_STD_ZVAL(retval);
467: array_init(retval);
468:
469: if (xpathobjp->type == XPATH_NODESET && NULL != (nodesetp = xpathobjp->nodesetval)) {
470:
471: for (i = 0; i < nodesetp->nodeNr; i++) {
472: xmlNodePtr node = nodesetp->nodeTab[i];
473: zval *child;
474:
475: MAKE_STD_ZVAL(child);
476:
477: if (node->type == XML_NAMESPACE_DECL) {
478: xmlNsPtr curns;
479: xmlNodePtr nsparent;
480:
481: nsparent = node->_private;
482: curns = xmlNewNs(NULL, node->name, NULL);
483: if (node->children) {
484: curns->prefix = xmlStrdup((char *) node->children);
485: }
486: if (node->children) {
487: node = xmlNewDocNode(docp, NULL, (char *) node->children, node->name);
488: } else {
489: node = xmlNewDocNode(docp, NULL, "xmlns", node->name);
490: }
491: node->type = XML_NAMESPACE_DECL;
492: node->parent = nsparent;
493: node->ns = curns;
494: }
495: child = php_dom_create_object(node, &ret, NULL, child, (dom_object *)intern TSRMLS_CC);
496: add_next_index_zval(retval, child);
497: }
498: }
499: php_dom_create_interator(return_value, DOM_NODELIST TSRMLS_CC);
500: nodeobj = (dom_object *)zend_objects_get_address(return_value TSRMLS_CC);
501: dom_xpath_iter(retval, nodeobj);
502: break;
503: }
504:
505: case XPATH_BOOLEAN:
506: RETVAL_BOOL(xpathobjp->boolval);
507: break;
508:
509: case XPATH_NUMBER:
510: RETVAL_DOUBLE(xpathobjp->floatval)
511: break;
512:
513: case XPATH_STRING:
514: RETVAL_STRING(xpathobjp->stringval, 1);
515: break;
516:
517: default:
518: RETVAL_NULL();
519: break;
520: }
521:
522: xmlXPathFreeObject(xpathobjp);
523: }
524: /* }}} */
525:
526: /* {{{ proto DOMNodeList dom_xpath_query(string expr [,DOMNode context [, boolean registerNodeNS]]); */
527: PHP_FUNCTION(dom_xpath_query)
528: {
529: php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_QUERY);
530: }
531: /* }}} end dom_xpath_query */
532:
533: /* {{{ proto mixed dom_xpath_evaluate(string expr [,DOMNode context [, boolean registerNodeNS]]); */
534: PHP_FUNCTION(dom_xpath_evaluate)
535: {
536: php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_EVALUATE);
537: }
538: /* }}} end dom_xpath_evaluate */
539:
540: /* {{{ proto void dom_xpath_register_php_functions() */
541: PHP_FUNCTION(dom_xpath_register_php_functions)
542: {
543: zval *id;
544: dom_xpath_object *intern;
545: zval *array_value, **entry, *new_string;
546: int name_len = 0;
547: char *name;
548:
549: DOM_GET_THIS(id);
550:
551: if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "a", &array_value) == SUCCESS) {
552: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
553: zend_hash_internal_pointer_reset(Z_ARRVAL_P(array_value));
554:
555: while (zend_hash_get_current_data(Z_ARRVAL_P(array_value), (void **)&entry) == SUCCESS) {
556: SEPARATE_ZVAL(entry);
557: convert_to_string_ex(entry);
558:
559: MAKE_STD_ZVAL(new_string);
560: ZVAL_LONG(new_string,1);
561:
562: zend_hash_update(intern->registered_phpfunctions, Z_STRVAL_PP(entry), Z_STRLEN_PP(entry) + 1, &new_string, sizeof(zval*), NULL);
563: zend_hash_move_forward(Z_ARRVAL_P(array_value));
564: }
565: intern->registerPhpFunctions = 2;
566: RETURN_TRUE;
567:
568: } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == SUCCESS) {
569: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
570:
571: MAKE_STD_ZVAL(new_string);
572: ZVAL_LONG(new_string,1);
573: zend_hash_update(intern->registered_phpfunctions, name, name_len + 1, &new_string, sizeof(zval*), NULL);
574: intern->registerPhpFunctions = 2;
575:
576: } else {
577: intern = (dom_xpath_object *)zend_object_store_get_object(id TSRMLS_CC);
578: intern->registerPhpFunctions = 1;
579: }
580:
581: }
582: /* }}} end dom_xpath_register_php_functions */
583:
584: #endif /* LIBXML_XPATH_ENABLED */
585:
586: #endif
587:
588: /*
589: * Local variables:
590: * tab-width: 4
591: * c-basic-offset: 4
592: * End:
593: * vim600: noet sw=4 ts=4 fdm=marker
594: * vim<600: noet sw=4 ts=4
595: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>