Project

General

Profile

Download (21.2 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2

    
3
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4

    
5
/**
6
 * Server commands for our PHP implementation of the XML-RPC protocol
7
 *
8
 * This is a PEAR-ified version of Useful inc's XML-RPC for PHP.
9
 * It has support for HTTP transport, proxies and authentication.
10
 *
11
 * PHP versions 4 and 5
12
 *
13
 * LICENSE: License is granted to use or modify this software
14
 * ("XML-RPC for PHP") for commercial or non-commercial use provided the
15
 * copyright of the author is preserved in any distributed or derivative work.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESSED OR
18
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 *
28
 * @category   Web Services
29
 * @package    XML_RPC
30
 * @author     Edd Dumbill <edd@usefulinc.com>
31
 * @author     Stig Bakken <stig@php.net>
32
 * @author     Martin Jansen <mj@php.net>
33
 * @author     Daniel Convissor <danielc@php.net>
34
 * @copyright  1999-2001 Edd Dumbill, 2001-2005 The PHP Group
35
 * @version    CVS: $Id$
36
 * @link       http://pear.php.net/package/XML_RPC
37
 */
38

    
39

    
40
/**
41
 * Pull in the XML_RPC class
42
 */
43
require_once 'xmlrpc_client.inc';
44

    
45

    
46
/**
47
 * signature for system.listMethods: return = array,
48
 * parameters = a string or nothing
49
 * @global array $GLOBALS['XML_RPC_Server_listMethods_sig']
50
 */
51
$GLOBALS['XML_RPC_Server_listMethods_sig'] = array(
52
    array($GLOBALS['XML_RPC_Array'],
53
          $GLOBALS['XML_RPC_String']
54
    ),
55
    array($GLOBALS['XML_RPC_Array'])
56
);
57

    
58
/**
59
 * docstring for system.listMethods
60
 * @global string $GLOBALS['XML_RPC_Server_listMethods_doc']
61
 */
62
$GLOBALS['XML_RPC_Server_listMethods_doc'] = 'This method lists all the'
63
        . ' methods that the XML-RPC server knows how to dispatch';
64

    
65
/**
66
 * signature for system.methodSignature: return = array,
67
 * parameters = string
68
 * @global array $GLOBALS['XML_RPC_Server_methodSignature_sig']
69
 */
70
$GLOBALS['XML_RPC_Server_methodSignature_sig'] = array(
71
    array($GLOBALS['XML_RPC_Array'],
72
          $GLOBALS['XML_RPC_String']
73
    )
74
);
75

    
76
/**
77
 * docstring for system.methodSignature
78
 * @global string $GLOBALS['XML_RPC_Server_methodSignature_doc']
79
 */
80
$GLOBALS['XML_RPC_Server_methodSignature_doc'] = 'Returns an array of known'
81
        . ' signatures (an array of arrays) for the method name passed. If'
82
        . ' no signatures are known, returns a none-array (test for type !='
83
        . ' array to detect missing signature)';
84

    
85
/**
86
 * signature for system.methodHelp: return = string,
87
 * parameters = string
88
 * @global array $GLOBALS['XML_RPC_Server_methodHelp_sig']
89
 */
90
$GLOBALS['XML_RPC_Server_methodHelp_sig'] = array(
91
    array($GLOBALS['XML_RPC_String'],
92
          $GLOBALS['XML_RPC_String']
93
    )
94
);
95

    
96
/**
97
 * docstring for methodHelp
98
 * @global string $GLOBALS['XML_RPC_Server_methodHelp_doc']
99
 */
100
$GLOBALS['XML_RPC_Server_methodHelp_doc'] = 'Returns help text if defined'
101
        . ' for the method passed, otherwise returns an empty string';
102

    
103
/**
104
 * dispatch map for the automatically declared XML-RPC methods.
105
 * @global array $GLOBALS['XML_RPC_Server_dmap']
106
 */
107
$GLOBALS['XML_RPC_Server_dmap'] = array(
108
    'system.listMethods' => array(
109
        'function'  => 'XML_RPC_Server_listMethods',
110
        'signature' => $GLOBALS['XML_RPC_Server_listMethods_sig'],
111
        'docstring' => $GLOBALS['XML_RPC_Server_listMethods_doc']
112
    ),
113
    'system.methodHelp' => array(
114
        'function'  => 'XML_RPC_Server_methodHelp',
115
        'signature' => $GLOBALS['XML_RPC_Server_methodHelp_sig'],
116
        'docstring' => $GLOBALS['XML_RPC_Server_methodHelp_doc']
117
    ),
118
    'system.methodSignature' => array(
119
        'function'  => 'XML_RPC_Server_methodSignature',
120
        'signature' => $GLOBALS['XML_RPC_Server_methodSignature_sig'],
121
        'docstring' => $GLOBALS['XML_RPC_Server_methodSignature_doc']
122
    )
123
);
124

    
125
/**
126
 * @global string $GLOBALS['XML_RPC_Server_debuginfo']
127
 */
128
$GLOBALS['XML_RPC_Server_debuginfo'] = '';
129

    
130

    
131
/**
132
 * Lists all the methods that the XML-RPC server knows how to dispatch
133
 *
134
 * @return object  a new XML_RPC_Response object
135
 */
136
function XML_RPC_Server_listMethods($server, $m)
137
{
138
    global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap;
139

    
140
    $v = new XML_RPC_Value();
141
    $outAr = array();
142
    foreach ($server->dmap as $key => $val) {
143
        $outAr[] = new XML_RPC_Value($key, 'string');
144
    }
145
    foreach ($XML_RPC_Server_dmap as $key => $val) {
146
        $outAr[] = new XML_RPC_Value($key, 'string');
147
    }
148
    $v->addArray($outAr);
149
    return new XML_RPC_Response($v);
150
}
151

    
152
/**
153
 * Returns an array of known signatures (an array of arrays)
154
 * for the given method
155
 *
156
 * If no signatures are known, returns a none-array
157
 * (test for type != array to detect missing signature)
158
 *
159
 * @return object  a new XML_RPC_Response object
160
 */
161
function XML_RPC_Server_methodSignature($server, $m)
162
{
163
    global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap;
164

    
165
    $methName = $m->getParam(0);
166
    $methName = $methName->scalarval();
167
    if (strpos($methName, 'system.') === 0) {
168
        $dmap = $XML_RPC_Server_dmap;
169
        $sysCall = 1;
170
    } else {
171
        $dmap = $server->dmap;
172
        $sysCall = 0;
173
    }
174
    //  print "<!-- ${methName} -->\n";
175
    if (isset($dmap[$methName])) {
176
        if ($dmap[$methName]['signature']) {
177
            $sigs = array();
178
            $thesigs = $dmap[$methName]['signature'];
179
            for ($i = 0; $i < sizeof($thesigs); $i++) {
180
                $cursig = array();
181
                $inSig = $thesigs[$i];
182
                for ($j = 0; $j < sizeof($inSig); $j++) {
183
                    $cursig[] = new XML_RPC_Value($inSig[$j], 'string');
184
                }
185
                $sigs[] = new XML_RPC_Value($cursig, 'array');
186
            }
187
            $r = new XML_RPC_Response(new XML_RPC_Value($sigs, 'array'));
188
        } else {
189
            $r = new XML_RPC_Response(new XML_RPC_Value('undef', 'string'));
190
        }
191
    } else {
192
        $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'],
193
                                  $XML_RPC_str['introspect_unknown']);
194
    }
195
    return $r;
196
}
197

    
198
/**
199
 * Returns help text if defined for the method passed, otherwise returns
200
 * an empty string
201
 *
202
 * @return object  a new XML_RPC_Response object
203
 */
204
function XML_RPC_Server_methodHelp($server, $m)
205
{
206
    global $XML_RPC_err, $XML_RPC_str, $XML_RPC_Server_dmap;
207

    
208
    $methName = $m->getParam(0);
209
    $methName = $methName->scalarval();
210
    if (strpos($methName, 'system.') === 0) {
211
        $dmap = $XML_RPC_Server_dmap;
212
        $sysCall = 1;
213
    } else {
214
        $dmap = $server->dmap;
215
        $sysCall = 0;
216
    }
217

    
218
    if (isset($dmap[$methName])) {
219
        if ($dmap[$methName]['docstring']) {
220
            $r = new XML_RPC_Response(new XML_RPC_Value($dmap[$methName]['docstring']),
221
                                                        'string');
222
        } else {
223
            $r = new XML_RPC_Response(new XML_RPC_Value('', 'string'));
224
        }
225
    } else {
226
        $r = new XML_RPC_Response(0, $XML_RPC_err['introspect_unknown'],
227
                                     $XML_RPC_str['introspect_unknown']);
228
    }
229
    return $r;
230
}
231

    
232
/**
233
 * @return void
234
 */
235
function XML_RPC_Server_debugmsg($m)
236
{
237
    global $XML_RPC_Server_debuginfo;
238
    $XML_RPC_Server_debuginfo = $XML_RPC_Server_debuginfo . $m . "\n";
239
}
240

    
241

    
242
/**
243
 * A server for receiving and replying to XML RPC requests
244
 *
245
 * <code>
246
 * $server = new XML_RPC_Server(
247
 *     array(
248
 *         'isan8' =>
249
 *             array(
250
 *                 'function' => 'is_8',
251
 *                 'signature' =>
252
 *                      array(
253
 *                          array('boolean', 'int'),
254
 *                          array('boolean', 'int', 'boolean'),
255
 *                          array('boolean', 'string'),
256
 *                          array('boolean', 'string', 'boolean'),
257
 *                      ),
258
 *                 'docstring' => 'Is the value an 8?'
259
 *             ),
260
 *     ),
261
 *     1,
262
 *     0
263
 * ); 
264
 * </code>
265
 *
266
 * @category   Web Services
267
 * @package    XML_RPC
268
 * @author     Edd Dumbill <edd@usefulinc.com>
269
 * @author     Stig Bakken <stig@php.net>
270
 * @author     Martin Jansen <mj@php.net>
271
 * @author     Daniel Convissor <danielc@php.net>
272
 * @copyright  1999-2001 Edd Dumbill, 2001-2005 The PHP Group
273
 * @version    Release: 1.4.5
274
 * @link       http://pear.php.net/package/XML_RPC
275
 */
276
class XML_RPC_Server
277
{
278
    /**
279
     * The dispatch map, listing the methods this server provides.
280
     * @var array
281
     */
282
    var $dmap = array();
283

    
284
    /**
285
     * The present response's encoding
286
     * @var string
287
     * @see XML_RPC_Message::getEncoding()
288
     */
289
    var $encoding = '';
290

    
291
    /**
292
     * Debug mode (0 = off, 1 = on)
293
     * @var integer
294
     */
295
    var $debug = 0;
296

    
297
    /**
298
     * The response's HTTP headers
299
     * @var string
300
     */
301
    var $server_headers = '';
302

    
303
    /**
304
     * The response's XML payload
305
     * @var string
306
     */
307
    var $server_payload = '';
308

    
309

    
310
    /**
311
     * Constructor for the XML_RPC_Server class
312
     *
313
     * @param array $dispMap   the dispatch map. An associative array
314
     *                          explaining each function. The keys of the main
315
     *                          array are the procedure names used by the
316
     *                          clients. The value is another associative array
317
     *                          that contains up to three elements:
318
     *                            + The 'function' element's value is the name
319
     *                              of the function or method that gets called.
320
     *                              To define a class' method: 'class::method'.
321
     *                            + The 'signature' element (optional) is an
322
     *                              array describing the return values and
323
     *                              parameters
324
     *                            + The 'docstring' element (optional) is a
325
     *                              string describing what the method does
326
     * @param int $serviceNow  should the HTTP response be sent now?
327
     *                          (1 = yes, 0 = no)
328
     * @param int $debug       should debug output be displayed?
329
     *                          (1 = yes, 0 = no)
330
     *
331
     * @return void
332
     */
333
    function XML_RPC_Server($dispMap, $serviceNow = 1, $debug = 0)
334
    {
335
        global $HTTP_RAW_POST_DATA;
336

    
337
        if ($debug) {
338
            $this->debug = 1;
339
        } else {
340
            $this->debug = 0;
341
        }
342

    
343
        $this->dmap = $dispMap;
344

    
345
        if ($serviceNow) {
346
            $this->service();
347
        } else {
348
            $this->createServerPayload();
349
            $this->createServerHeaders();
350
        }
351
    }
352

    
353
    /**
354
     * @return string  the debug information if debug debug mode is on
355
     */
356
    function serializeDebug()
357
    {
358
        global $XML_RPC_Server_debuginfo, $HTTP_RAW_POST_DATA;
359

    
360
        if ($this->debug) {
361
            XML_RPC_Server_debugmsg('vvv POST DATA RECEIVED BY SERVER vvv' . "\n"
362
                                    . $HTTP_RAW_POST_DATA
363
                                    . "\n" . '^^^ END POST DATA ^^^');
364
        }
365

    
366
        if ($XML_RPC_Server_debuginfo != '') {
367
            return "<!-- PEAR XML_RPC SERVER DEBUG INFO:\n\n"
368
                   . preg_replace('/-(?=-)/', '- ', $XML_RPC_Server_debuginfo)
369
                   . "-->\n";
370
        } else {
371
            return '';
372
        }
373
    }
374

    
375
    /**
376
     * Sends the response
377
     *
378
     * The encoding and content-type are determined by
379
     * XML_RPC_Message::getEncoding()
380
     *
381
     * @return void
382
     *
383
     * @uses XML_RPC_Server::createServerPayload(),
384
     *       XML_RPC_Server::createServerHeaders()
385
     */
386
    function service()
387
    {
388
        if (!$this->server_payload) {
389
            $this->createServerPayload();
390
        }
391
        if (!$this->server_headers) {
392
            $this->createServerHeaders();
393
        }
394

    
395
        /*
396
         * $server_headers needs to remain a string for compatibility with
397
         * old scripts using this package, but PHP 4.4.2 no longer allows
398
         * line breaks in header() calls.  So, we split each header into
399
         * an individual call.  The initial replace handles the off chance
400
         * that someone composed a single header with multiple lines, which
401
         * the RFCs allow.
402
         */
403
        $this->server_headers = preg_replace("/[\r\n]+[ \t]+/", ' ',
404
                                             trim($this->server_headers));
405
        $headers = preg_split("/[\r\n]+/", $this->server_headers);
406
        foreach ($headers as $header)
407
        {
408
            header($header);
409
        }
410

    
411
        print $this->server_payload;
412
    }
413

    
414
    /**
415
     * Generates the payload and puts it in the $server_payload property
416
     *
417
     * @return void
418
     *
419
     * @uses XML_RPC_Server::parseRequest(), XML_RPC_Server::$encoding,
420
     *       XML_RPC_Response::serialize(), XML_RPC_Server::serializeDebug()
421
     */
422
    function createServerPayload()
423
    {
424
        $r = $this->parseRequest();
425
        $this->server_payload = '<?xml version="1.0" encoding="'
426
                              . $this->encoding . '"?>' . "\n"
427
                              . $this->serializeDebug()
428
                              . $r->serialize();
429
    }
430

    
431
    /**
432
     * Determines the HTTP headers and puts them in the $server_headers
433
     * property
434
     *
435
     * @return boolean  TRUE if okay, FALSE if $server_payload isn't set.
436
     *
437
     * @uses XML_RPC_Server::createServerPayload(),
438
     *       XML_RPC_Server::$server_headers
439
     */
440
    function createServerHeaders()
441
    {
442
        if (!$this->server_payload) {
443
            return false;
444
        }
445
        $this->server_headers = 'Content-Length: '
446
                              . strlen($this->server_payload) . "\r\n"
447
                              . 'Content-Type: text/xml;'
448
                              . ' charset=' . $this->encoding;
449
        return true;
450
    }
451

    
452
    /**
453
     * @return array
454
     */
455
    function verifySignature($in, $sig)
456
    {
457
        for ($i = 0; $i < sizeof($sig); $i++) {
458
            // check each possible signature in turn
459
            $cursig = $sig[$i];
460
            if (sizeof($cursig) == $in->getNumParams() + 1) {
461
                $itsOK = 1;
462
                for ($n = 0; $n < $in->getNumParams(); $n++) {
463
                    $p = $in->getParam($n);
464
                    // print "<!-- $p -->\n";
465
                    if ($p->kindOf() == 'scalar') {
466
                        $pt = $p->scalartyp();
467
                    } else {
468
                        $pt = $p->kindOf();
469
                    }
470
                    // $n+1 as first type of sig is return type
471
                    if ($pt != $cursig[$n+1]) {
472
                        $itsOK = 0;
473
                        $pno = $n+1;
474
                        $wanted = $cursig[$n+1];
475
                        $got = $pt;
476
                        break;
477
                    }
478
                }
479
                if ($itsOK) {
480
                    return array(1);
481
                }
482
            }
483
        }
484
        if (isset($wanted)) {
485
            return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
486
        } else {
487
            $allowed = array();
488
            foreach ($sig as $val) {
489
                end($val);
490
                $allowed[] = key($val);
491
            }
492
            $allowed = array_unique($allowed);
493
            $last = count($allowed) - 1;
494
            if ($last > 0) {
495
                $allowed[$last] = 'or ' . $allowed[$last];
496
            }
497
            return array(0,
498
                         'Signature permits ' . implode(', ', $allowed)
499
                                . ' parameters but the request had '
500
                                . $in->getNumParams());
501
        }
502
    }
503

    
504
    /**
505
     * @return object  a new XML_RPC_Response object
506
     *
507
     * @uses XML_RPC_Message::getEncoding(), XML_RPC_Server::$encoding
508
     */
509
    function parseRequest($data = '')
510
    {
511
        global $XML_RPC_xh, $HTTP_RAW_POST_DATA,
512
                $XML_RPC_err, $XML_RPC_str, $XML_RPC_errxml,
513
                $XML_RPC_defencoding, $XML_RPC_Server_dmap;
514

    
515
        if ($data == '') {
516
            $data = $HTTP_RAW_POST_DATA;
517
        }
518

    
519
        $this->encoding = XML_RPC_Message::getEncoding($data);
520
        $parser_resource = xml_parser_create($this->encoding);
521
        $parser = (int) $parser_resource;
522

    
523
        $XML_RPC_xh[$parser] = array();
524
        $XML_RPC_xh[$parser]['cm']     = 0;
525
        $XML_RPC_xh[$parser]['isf']    = 0;
526
        $XML_RPC_xh[$parser]['params'] = array();
527
        $XML_RPC_xh[$parser]['method'] = '';
528
        $XML_RPC_xh[$parser]['stack'] = array();	
529
        $XML_RPC_xh[$parser]['valuestack'] = array();	
530

    
531
        $plist = '';
532

    
533
        // decompose incoming XML into request structure
534

    
535
        xml_parser_set_option($parser_resource, XML_OPTION_CASE_FOLDING, true);
536
        xml_set_element_handler($parser_resource, 'XML_RPC_se', 'XML_RPC_ee');
537
        xml_set_character_data_handler($parser_resource, 'XML_RPC_cd');
538
        if (!xml_parse($parser_resource, $data, 1)) {
539
            // return XML error as a faultCode
540
            $r = new XML_RPC_Response(0,
541
                                      $XML_RPC_errxml+xml_get_error_code($parser_resource),
542
                                      sprintf('XML error: %s at line %d',
543
                                              xml_error_string(xml_get_error_code($parser_resource)),
544
                                              xml_get_current_line_number($parser_resource)));
545
            xml_parser_free($parser_resource);
546
        } elseif ($XML_RPC_xh[$parser]['isf']>1) {
547
            $r = new XML_RPC_Response(0,
548
                                      $XML_RPC_err['invalid_request'],
549
                                      $XML_RPC_str['invalid_request']
550
                                      . ': '
551
                                      . $XML_RPC_xh[$parser]['isf_reason']);
552
            xml_parser_free($parser_resource);
553
        } else {
554
            xml_parser_free($parser_resource);
555
            $m = new XML_RPC_Message($XML_RPC_xh[$parser]['method']);
556
            // now add parameters in
557
            for ($i = 0; $i < sizeof($XML_RPC_xh[$parser]['params']); $i++) {
558
                // print '<!-- ' . $XML_RPC_xh[$parser]['params'][$i]. "-->\n";
559
                $plist .= "$i - " . var_export($XML_RPC_xh[$parser]['params'][$i], true) . " \n";
560
                $m->addParam($XML_RPC_xh[$parser]['params'][$i]);
561
            }
562

    
563
            if ($this->debug) {
564
                XML_RPC_Server_debugmsg($plist);
565
            }
566

    
567
            // now to deal with the method
568
            $methName = $XML_RPC_xh[$parser]['method'];
569
            if (strpos($methName, 'system.') === 0) {
570
                $dmap = $XML_RPC_Server_dmap;
571
                $sysCall = 1;
572
            } else {
573
                $dmap = $this->dmap;
574
                $sysCall = 0;
575
            }
576

    
577
            if (isset($dmap[$methName]['function'])
578
                && is_string($dmap[$methName]['function'])
579
                && strpos($dmap[$methName]['function'], '::') !== false)
580
            {
581
                $dmap[$methName]['function'] =
582
                        explode('::', $dmap[$methName]['function']);
583
            }
584

    
585
            if (isset($dmap[$methName]['function'])
586
                && is_callable($dmap[$methName]['function']))
587
            {
588
                // dispatch if exists
589
                if (isset($dmap[$methName]['signature'])) {
590
                    $sr = $this->verifySignature($m,
591
                                                 $dmap[$methName]['signature'] );
592
                }
593
                if (!isset($dmap[$methName]['signature']) || $sr[0]) {
594
                    // if no signature or correct signature
595
                    if ($sysCall) {
596
                        $r = call_user_func($dmap[$methName]['function'], $this, $m);
597
                    } else {
598
                        $r = call_user_func($dmap[$methName]['function'], $m);
599
                    }
600
                    if (!is_a($r, 'XML_RPC_Response')) {
601
                        $r = new XML_RPC_Response(0, $XML_RPC_err['not_response_object'],
602
                                                  $XML_RPC_str['not_response_object']);
603
                    }
604
                } else {
605
                    $r = new XML_RPC_Response(0, $XML_RPC_err['incorrect_params'],
606
                                              $XML_RPC_str['incorrect_params']
607
                                              . ': ' . $sr[1]);
608
                }
609
            } else {
610
                // else prepare error response
611
                $r = new XML_RPC_Response(0, $XML_RPC_err['unknown_method'],
612
                                          $XML_RPC_str['unknown_method']);
613
            }
614
        }
615
        return $r;
616
    }
617

    
618
    /**
619
     * Echos back the input packet as a string value
620
     *
621
     * @return void
622
     *
623
     * Useful for debugging.
624
     */
625
    function echoInput()
626
    {
627
        global $HTTP_RAW_POST_DATA;
628

    
629
        $r = new XML_RPC_Response(0);
630
        $r->xv = new XML_RPC_Value("'Aha said I: '" . $HTTP_RAW_POST_DATA, 'string');
631
        print $r->serialize();
632
    }
633
}
634

    
635
/*
636
 * Local variables:
637
 * tab-width: 4
638
 * c-basic-offset: 4
639
 * c-hanging-comment-ender-p: nil
640
 * End:
641
 */
642

    
643
?>
(27-27/27)