1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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 | Author: Sterling Hughes <sterling@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* $Id$ */
20
21 #define ZEND_INCLUDE_FULL_WINDOWS_HEADERS
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "php.h"
28
29 #if HAVE_CURL
30
31 #include "php_curl.h"
32
33 #include <curl/curl.h>
34 #include <curl/multi.h>
35
36 #ifdef HAVE_SYS_SELECT_H
37 #include <sys/select.h>
38 #endif
39
40 #ifdef HAVE_SYS_TIME_H
41 #include <sys/time.h>
42 #endif
43
44 #ifdef HAVE_SYS_TYPES_H
45 #include <sys/types.h>
46 #endif
47
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h>
50 #endif
51
52 #define SAVE_CURLM_ERROR(__handle, __err) (__handle)->err.no = (int) __err;
53
54 /* {{{ proto resource curl_multi_init(void)
55 Returns a new cURL multi handle */
PHP_FUNCTION(curl_multi_init)56 PHP_FUNCTION(curl_multi_init)
57 {
58 php_curlm *mh;
59
60 if (zend_parse_parameters_none() == FAILURE) {
61 return;
62 }
63
64 mh = ecalloc(1, sizeof(php_curlm));
65 mh->multi = curl_multi_init();
66 mh->handlers = ecalloc(1, sizeof(php_curlm_handlers));
67
68 zend_llist_init(&mh->easyh, sizeof(zval), _php_curl_multi_cleanup_list, 0);
69
70 RETURN_RES(zend_register_resource(mh, le_curl_multi_handle));
71 }
72 /* }}} */
73
74 /* {{{ proto int curl_multi_add_handle(resource mh, resource ch)
75 Add a normal cURL handle to a cURL multi handle */
PHP_FUNCTION(curl_multi_add_handle)76 PHP_FUNCTION(curl_multi_add_handle)
77 {
78 zval *z_mh;
79 zval *z_ch;
80 php_curlm *mh;
81 php_curl *ch;
82 zval tmp_val;
83 CURLMcode error = CURLM_OK;
84
85 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &z_mh, &z_ch) == FAILURE) {
86 return;
87 }
88
89 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
90 RETURN_FALSE;
91 }
92
93 if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
94 RETURN_FALSE;
95 }
96
97 _php_curl_cleanup_handle(ch);
98
99 /* we want to create a copy of this zval that we store in the multihandle structure element "easyh" */
100 ZVAL_DUP(&tmp_val, z_ch);
101
102 zend_llist_add_element(&mh->easyh, &tmp_val);
103
104 error = curl_multi_add_handle(mh->multi, ch->cp);
105 SAVE_CURLM_ERROR(mh, error);
106
107 RETURN_LONG((zend_long) error);
108 }
109 /* }}} */
110
_php_curl_multi_cleanup_list(void * data)111 void _php_curl_multi_cleanup_list(void *data) /* {{{ */
112 {
113 zval *z_ch = (zval *)data;
114 php_curl *ch;
115
116 if (!z_ch) {
117 return;
118 }
119 if (!Z_RES_P(z_ch)->ptr) {
120 return;
121 }
122 if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
123 return;
124 }
125
126 zend_list_delete(Z_RES_P(z_ch));
127 }
128 /* }}} */
129
130 /* Used internally as comparison routine passed to zend_list_del_element */
curl_compare_resources(zval * z1,zval * z2)131 static int curl_compare_resources( zval *z1, zval *z2 ) /* {{{ */
132 {
133 return (Z_TYPE_P(z1) == Z_TYPE_P(z2) &&
134 Z_TYPE_P(z1) == IS_RESOURCE &&
135 Z_RES_P(z1) == Z_RES_P(z2));
136 }
137 /* }}} */
138
139 /* Used to find the php_curl resource for a given curl easy handle */
_php_curl_multi_find_easy_handle(php_curlm * mh,CURL * easy)140 static zval *_php_curl_multi_find_easy_handle(php_curlm *mh, CURL *easy) /* {{{ */
141 {
142 php_curl *tmp_ch;
143 zend_llist_position pos;
144 zval *pz_ch_temp;
145
146 for(pz_ch_temp = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch_temp;
147 pz_ch_temp = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
148
149 if ((tmp_ch = (php_curl *)zend_fetch_resource(Z_RES_P(pz_ch_temp), le_curl_name, le_curl)) == NULL) {
150 return NULL;
151 }
152
153 if (tmp_ch->cp == easy) {
154 return pz_ch_temp;
155 }
156 }
157
158 return NULL;
159 }
160 /* }}} */
161
162 /* {{{ proto int curl_multi_remove_handle(resource mh, resource ch)
163 Remove a multi handle from a set of cURL handles */
PHP_FUNCTION(curl_multi_remove_handle)164 PHP_FUNCTION(curl_multi_remove_handle)
165 {
166 zval *z_mh;
167 zval *z_ch;
168 php_curlm *mh;
169 php_curl *ch;
170 CURLMcode error = CURLM_OK;
171
172 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &z_mh, &z_ch) == FAILURE) {
173 return;
174 }
175
176 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
177 RETURN_FALSE;
178 }
179
180 if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
181 RETURN_FALSE;
182 }
183
184 error = curl_multi_remove_handle(mh->multi, ch->cp);
185 SAVE_CURLM_ERROR(mh, error);
186
187 RETVAL_LONG((zend_long) error);
188 zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *))curl_compare_resources);
189
190 }
191 /* }}} */
192
193 #if LIBCURL_VERSION_NUM < 0x071c00
_make_timeval_struct(struct timeval * to,double timeout)194 static void _make_timeval_struct(struct timeval *to, double timeout) /* {{{ */
195 {
196 unsigned long conv;
197
198 conv = (unsigned long) (timeout * 1000000.0);
199 to->tv_sec = conv / 1000000;
200 to->tv_usec = conv % 1000000;
201 }
202 /* }}} */
203 #endif
204
205 /* {{{ proto int curl_multi_select(resource mh[, double timeout])
206 Get all the sockets associated with the cURL extension, which can then be "selected" */
PHP_FUNCTION(curl_multi_select)207 PHP_FUNCTION(curl_multi_select)
208 {
209 zval *z_mh;
210 php_curlm *mh;
211 double timeout = 1.0;
212 #if LIBCURL_VERSION_NUM >= 0x071c00 /* Available since 7.28.0 */
213 int numfds = 0;
214 #else
215 fd_set readfds;
216 fd_set writefds;
217 fd_set exceptfds;
218 int maxfd;
219 struct timeval to;
220 #endif
221 CURLMcode error = CURLM_OK;
222
223 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|d", &z_mh, &timeout) == FAILURE) {
224 return;
225 }
226
227 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
228 RETURN_FALSE;
229 }
230
231 #if LIBCURL_VERSION_NUM >= 0x071c00 /* Available since 7.28.0 */
232 error = curl_multi_wait(mh->multi, NULL, 0, (unsigned long) timeout * 1000.0, &numfds);
233 if (CURLM_OK != error) {
234 SAVE_CURLM_ERROR(mh, error);
235 RETURN_LONG(-1);
236 }
237
238 RETURN_LONG(numfds);
239 #else
240 _make_timeval_struct(&to, timeout);
241
242 FD_ZERO(&readfds);
243 FD_ZERO(&writefds);
244 FD_ZERO(&exceptfds);
245
246 error = curl_multi_fdset(mh->multi, &readfds, &writefds, &exceptfds, &maxfd);
247 SAVE_CURLM_ERROR(mh, error);
248
249 if (maxfd == -1) {
250 RETURN_LONG(-1);
251 }
252 RETURN_LONG(select(maxfd + 1, &readfds, &writefds, &exceptfds, &to));
253 #endif
254 }
255 /* }}} */
256
257 /* {{{ proto int curl_multi_exec(resource mh, int &still_running)
258 Run the sub-connections of the current cURL handle */
PHP_FUNCTION(curl_multi_exec)259 PHP_FUNCTION(curl_multi_exec)
260 {
261 zval *z_mh;
262 zval *z_still_running;
263 php_curlm *mh;
264 int still_running;
265 CURLMcode error = CURLM_OK;
266
267 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz/", &z_mh, &z_still_running) == FAILURE) {
268 return;
269 }
270
271 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
272 RETURN_FALSE;
273 }
274
275 {
276 zend_llist_position pos;
277 php_curl *ch;
278 zval *pz_ch;
279
280 for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
281 pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
282
283 if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(pz_ch), le_curl_name, le_curl)) == NULL) {
284 RETURN_FALSE;
285 }
286
287 _php_curl_verify_handlers(ch, 1);
288 }
289 }
290
291 convert_to_long(z_still_running);
292 still_running = Z_LVAL_P(z_still_running);
293 error = curl_multi_perform(mh->multi, &still_running);
294 ZVAL_LONG(z_still_running, still_running);
295
296 SAVE_CURLM_ERROR(mh, error);
297 RETURN_LONG((zend_long) error);
298 }
299 /* }}} */
300
301 /* {{{ proto string curl_multi_getcontent(resource ch)
302 Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */
PHP_FUNCTION(curl_multi_getcontent)303 PHP_FUNCTION(curl_multi_getcontent)
304 {
305 zval *z_ch;
306 php_curl *ch;
307
308 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &z_ch) == FAILURE) {
309 return;
310 }
311
312 if ((ch = (php_curl *)zend_fetch_resource(Z_RES_P(z_ch), le_curl_name, le_curl)) == NULL) {
313 RETURN_FALSE;
314 }
315
316 if (ch->handlers->write->method == PHP_CURL_RETURN) {
317 if (!ch->handlers->write->buf.s) {
318 RETURN_EMPTY_STRING();
319 }
320 smart_str_0(&ch->handlers->write->buf);
321 RETURN_STR_COPY(ch->handlers->write->buf.s);
322 }
323
324 RETURN_NULL();
325 }
326 /* }}} */
327
328 /* {{{ proto array curl_multi_info_read(resource mh [, long msgs_in_queue])
329 Get information about the current transfers */
PHP_FUNCTION(curl_multi_info_read)330 PHP_FUNCTION(curl_multi_info_read)
331 {
332 zval *z_mh;
333 php_curlm *mh;
334 CURLMsg *tmp_msg;
335 int queued_msgs;
336 zval *zmsgs_in_queue = NULL;
337
338 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z/", &z_mh, &zmsgs_in_queue) == FAILURE) {
339 return;
340 }
341
342 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
343 RETURN_FALSE;
344 }
345
346 tmp_msg = curl_multi_info_read(mh->multi, &queued_msgs);
347 if (tmp_msg == NULL) {
348 RETURN_FALSE;
349 }
350 if (zmsgs_in_queue) {
351 zval_ptr_dtor(zmsgs_in_queue);
352 ZVAL_LONG(zmsgs_in_queue, queued_msgs);
353 }
354
355 array_init(return_value);
356 add_assoc_long(return_value, "msg", tmp_msg->msg);
357 add_assoc_long(return_value, "result", tmp_msg->data.result);
358
359 /* find the original easy curl handle */
360 {
361 zval *pz_ch = _php_curl_multi_find_easy_handle(mh, tmp_msg->easy_handle);
362 if (pz_ch != NULL) {
363 /* we are adding a reference to the underlying php_curl
364 resource, so we need to add one to the resource's refcount
365 in order to ensure it doesn't get destroyed when the
366 underlying curl easy handle goes out of scope.
367 Normally you would call zval_copy_ctor( pz_ch ), or
368 SEPARATE_ZVAL, but those create new zvals, which is already
369 being done in add_assoc_resource */
370 Z_ADDREF_P(pz_ch);
371
372 /* add_assoc_resource automatically creates a new zval to
373 wrap the "resource" represented by the current pz_ch */
374
375 add_assoc_zval(return_value, "handle", pz_ch);
376 }
377 }
378 }
379 /* }}} */
380
381 /* {{{ proto void curl_multi_close(resource mh)
382 Close a set of cURL handles */
PHP_FUNCTION(curl_multi_close)383 PHP_FUNCTION(curl_multi_close)
384 {
385 zval *z_mh;
386 php_curlm *mh;
387
388 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &z_mh) == FAILURE) {
389 return;
390 }
391
392 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
393 RETURN_FALSE;
394 }
395
396 zend_list_close(Z_RES_P(z_mh));
397 }
398 /* }}} */
399
_php_curl_multi_close(zend_resource * rsrc)400 void _php_curl_multi_close(zend_resource *rsrc) /* {{{ */
401 {
402 php_curlm *mh = (php_curlm *)rsrc->ptr;
403 if (mh) {
404 zend_llist_position pos;
405 php_curl *ch;
406 zval *pz_ch;
407
408 for (pz_ch = (zval *)zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch;
409 pz_ch = (zval *)zend_llist_get_next_ex(&mh->easyh, &pos)) {
410 /* ptr is NULL means it already be freed */
411 if (Z_RES_P(pz_ch)->ptr) {
412 if ((ch = (php_curl *) zend_fetch_resource(Z_RES_P(pz_ch), le_curl_name, le_curl))) {
413 _php_curl_verify_handlers(ch, 0);
414 }
415 }
416 }
417
418 curl_multi_cleanup(mh->multi);
419 zend_llist_clean(&mh->easyh);
420 if (mh->handlers->server_push) {
421 efree(mh->handlers->server_push);
422 }
423 if (mh->handlers) {
424 efree(mh->handlers);
425 }
426 efree(mh);
427 rsrc->ptr = NULL;
428 }
429 }
430 /* }}} */
431
432 /* {{{ proto int curl_multi_errno(resource mh)
433 Return an integer containing the last multi curl error number */
PHP_FUNCTION(curl_multi_errno)434 PHP_FUNCTION(curl_multi_errno)
435 {
436 zval *z_mh;
437 php_curlm *mh;
438
439 if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &z_mh) == FAILURE) {
440 return;
441 }
442
443 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
444 RETURN_FALSE;
445 }
446
447 RETURN_LONG(mh->err.no);
448 }
449 /* }}} */
450
451 #if LIBCURL_VERSION_NUM >= 0x070c00 /* Available since 7.12.0 */
452 /* {{{ proto bool curl_multi_strerror(int code)
453 return string describing error code */
PHP_FUNCTION(curl_multi_strerror)454 PHP_FUNCTION(curl_multi_strerror)
455 {
456 zend_long code;
457 const char *str;
458
459 if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &code) == FAILURE) {
460 return;
461 }
462
463 str = curl_multi_strerror(code);
464 if (str) {
465 RETURN_STRING(str);
466 } else {
467 RETURN_NULL();
468 }
469 }
470 /* }}} */
471 #endif
472
473 #if LIBCURL_VERSION_NUM >= 0x072C00 /* Available since 7.44.0 */
474
_php_server_push_callback(CURL * parent_ch,CURL * easy,size_t num_headers,struct curl_pushheaders * push_headers,void * userp)475 static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_headers, struct curl_pushheaders *push_headers, void *userp) /* {{{ */
476 {
477 php_curl *ch;
478 php_curl *parent;
479 php_curlm *mh = (php_curlm *)userp;
480 size_t rval = CURL_PUSH_DENY;
481 php_curlm_server_push *t = mh->handlers->server_push;
482 zval *pz_parent_ch = NULL;
483 zval pz_ch;
484 zval headers;
485 zval retval;
486 zend_resource *res;
487 char *header;
488 int error;
489 zend_fcall_info fci = empty_fcall_info;
490
491 pz_parent_ch = _php_curl_multi_find_easy_handle(mh, parent_ch);
492 if (pz_parent_ch == NULL) {
493 return rval;
494 }
495
496 parent = (php_curl*)zend_fetch_resource(Z_RES_P(pz_parent_ch), le_curl_name, le_curl);
497
498 ch = alloc_curl_handle();
499 ch->cp = easy;
500 _php_setup_easy_copy_handlers(ch, parent);
501
502 Z_ADDREF_P(pz_parent_ch);
503
504 res = zend_register_resource(ch, le_curl);
505 ZVAL_RES(&pz_ch, res);
506
507 size_t i;
508 array_init(&headers);
509 for(i=0; i<num_headers; i++) {
510 header = curl_pushheader_bynum(push_headers, i);
511 add_next_index_string(&headers, header);
512 }
513
514 zend_fcall_info_init(&t->func_name, 0, &fci, &t->fci_cache, NULL, NULL);
515
516 zend_fcall_info_argn(
517 &fci, 3,
518 pz_parent_ch,
519 &pz_ch,
520 &headers
521 );
522
523 fci.retval = &retval;
524
525 error = zend_call_function(&fci, &t->fci_cache);
526 zend_fcall_info_args_clear(&fci, 1);
527 zval_dtor(&headers);
528
529 if (error == FAILURE) {
530 php_error_docref(NULL, E_WARNING, "Cannot call the CURLMOPT_PUSHFUNCTION");
531 } else if (!Z_ISUNDEF(retval)) {
532 if (CURL_PUSH_DENY != zval_get_long(&retval)) {
533 rval = CURL_PUSH_OK;
534
535 /* we want to create a copy of this zval that we store in the multihandle structure element "easyh" */
536 zval tmp_val;
537 ZVAL_DUP(&tmp_val, &pz_ch);
538 zend_llist_add_element(&mh->easyh, &tmp_val);
539 } else {
540 /* libcurl will free this easy handle, avoid double free */
541 ch->cp = NULL;
542 }
543 }
544
545 return rval;
546 }
547
548 #endif
549
550 #if LIBCURL_VERSION_NUM >= 0x070f04 /* 7.15.4 */
_php_curl_multi_setopt(php_curlm * mh,zend_long option,zval * zvalue,zval * return_value)551 static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */
552 {
553 CURLMcode error = CURLM_OK;
554
555 switch (option) {
556 #if LIBCURL_VERSION_NUM >= 0x071000 /* 7.16.0 */
557 case CURLMOPT_PIPELINING:
558 #endif
559 #if LIBCURL_VERSION_NUM >= 0x071003 /* 7.16.3 */
560 case CURLMOPT_MAXCONNECTS:
561 #endif
562 #if LIBCURL_VERSION_NUM >= 0x071e00 /* 7.30.0 */
563 case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE:
564 case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE:
565 case CURLMOPT_MAX_HOST_CONNECTIONS:
566 case CURLMOPT_MAX_PIPELINE_LENGTH:
567 case CURLMOPT_MAX_TOTAL_CONNECTIONS:
568 #endif
569 error = curl_multi_setopt(mh->multi, option, zval_get_long(zvalue));
570 break;
571 #if LIBCURL_VERSION_NUM > 0x072D00 /* Available since 7.46.0 */
572 case CURLMOPT_PUSHFUNCTION:
573 if (mh->handlers->server_push == NULL) {
574 mh->handlers->server_push = ecalloc(1, sizeof(php_curlm_server_push));
575 } else if (!Z_ISUNDEF(mh->handlers->server_push->func_name)) {
576 zval_ptr_dtor(&mh->handlers->server_push->func_name);
577 mh->handlers->server_push->fci_cache = empty_fcall_info_cache;
578 }
579
580 ZVAL_COPY(&mh->handlers->server_push->func_name, zvalue);
581 mh->handlers->server_push->method = PHP_CURL_USER;
582 if (!Z_ISUNDEF(mh->handlers->server_push->func_name)) {
583 zval_ptr_dtor(&mh->handlers->server_push->func_name);
584 mh->handlers->server_push->fci_cache = empty_fcall_info_cache;
585
586 }
587 error = curl_multi_setopt(mh->multi, option, _php_server_push_callback);
588 if (error != CURLM_OK) {
589 return 0;
590 }
591 error = curl_multi_setopt(mh->multi, CURLMOPT_PUSHDATA, mh);
592 break;
593 #endif
594 default:
595 php_error_docref(NULL, E_WARNING, "Invalid curl multi configuration option");
596 error = CURLM_UNKNOWN_OPTION;
597 break;
598 }
599
600 SAVE_CURLM_ERROR(mh, error);
601 if (error != CURLM_OK) {
602 return 1;
603 } else {
604 return 0;
605 }
606 }
607 /* }}} */
608
609 /* {{{ proto int curl_multi_setopt(resource mh, int option, mixed value)
610 Set an option for the curl multi handle */
PHP_FUNCTION(curl_multi_setopt)611 PHP_FUNCTION(curl_multi_setopt)
612 {
613 zval *z_mh, *zvalue;
614 zend_long options;
615 php_curlm *mh;
616
617 if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &z_mh, &options, &zvalue) == FAILURE) {
618 return;
619 }
620
621 if ((mh = (php_curlm *)zend_fetch_resource(Z_RES_P(z_mh), le_curl_multi_handle_name, le_curl_multi_handle)) == NULL) {
622 RETURN_FALSE;
623 }
624
625 if (!_php_curl_multi_setopt(mh, options, zvalue, return_value)) {
626 RETURN_TRUE;
627 } else {
628 RETURN_FALSE;
629 }
630 }
631 /* }}} */
632 #endif
633
634 #endif
635
636 /*
637 * Local variables:
638 * tab-width: 4
639 * c-basic-offset: 4
640 * End:
641 * vim600: noet sw=4 ts=4 fdm=marker
642 * vim<600: noet sw=4 ts=4
643 */
644