xref: /unit/src/nxt_php_sapi.c (revision 216:07257705cd64)
1 
2 /*
3  * Copyright (C) Max Romanov
4  * Copyright (C) Valentin V. Bartenev
5  * Copyright (C) NGINX, Inc.
6  */
7 
8 #include "php.h"
9 #include "SAPI.h"
10 #include "php_main.h"
11 #include "php_variables.h"
12 
13 #include <nxt_main.h>
14 #include <nxt_application.h>
15 
16 
17 static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf);
18 
19 static nxt_int_t nxt_php_run(nxt_task_t *task,
20                       nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg);
21 
22 #if PHP_MAJOR_VERSION >= 7
23 #   define NXT_PHP7 1
24 #   if PHP_MINOR_VERSION >= 1
25 #       define NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE 1
26 #   else
27 #       define NXT_HAVE_PHP_INTERRUPTS 1
28 #   endif
29 #   define NXT_HAVE_PHP_IGNORE_CWD 1
30 #else
31 #   define NXT_HAVE_PHP_INTERRUPTS 1
32 #   if PHP_MINOR_VERSION >= 4
33 #       define NXT_HAVE_PHP_IGNORE_CWD 1
34 #   endif
35 #endif
36 
37 static int nxt_php_startup(sapi_module_struct *sapi_module);
38 static int nxt_php_send_headers(sapi_headers_struct *sapi_headers);
39 static char *nxt_php_read_cookies(void);
40 static void nxt_php_register_variables(zval *track_vars_array);
41 static void nxt_php_log_message(char *message
42 #ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
43                                 , int syslog_type_int
44 #endif
45 );
46 
47 #ifdef NXT_PHP7
48 static size_t nxt_php_unbuffered_write(const char *str,
49     size_t str_length TSRMLS_DC);
50 static size_t nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC);
51 #else
52 static int nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC);
53 static int nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC);
54 #endif
55 
56 static void nxt_php_flush(void *server_context);
57 
58 
59 static sapi_module_struct  nxt_php_sapi_module =
60 {
61     (char *) "cli-server",
62     (char *) "nginext",
63 
64     nxt_php_startup,             /* startup */
65     php_module_shutdown_wrapper, /* shutdown */
66 
67     NULL,                        /* activate */
68     NULL,                        /* deactivate */
69 
70     nxt_php_unbuffered_write,    /* unbuffered write */
71     nxt_php_flush,               /* flush */
72     NULL,                        /* get uid */
73     NULL,                        /* getenv */
74 
75     php_error,                   /* error handler */
76 
77     NULL,                        /* header handler */
78     nxt_php_send_headers,        /* send headers handler */
79     NULL,                        /* send header handler */
80 
81     nxt_php_read_post,           /* read POST data */
82     nxt_php_read_cookies,        /* read Cookies */
83 
84     nxt_php_register_variables,  /* register server variables */
85     nxt_php_log_message,         /* log message */
86     NULL,                        /* get request time */
87     NULL,                        /* terminate process */
88 
89     NULL,                        /* php_ini_path_override */
90 #ifdef NXT_HAVE_PHP_INTERRUPTS
91     NULL,                        /* block_interruptions */
92     NULL,                        /* unblock_interruptions */
93 #endif
94     NULL,                        /* default_post_reader */
95     NULL,                        /* treat_data */
96     NULL,                        /* executable_location */
97 
98     0,                           /* php_ini_ignore */
99 #ifdef NXT_HAVE_PHP_IGNORE_CWD
100     0,                           /* php_ini_ignore_cwd */
101 #endif
102     NULL,                        /* get_fd */
103 
104     NULL,                        /* force_http_10 */
105 
106     NULL,                        /* get_target_uid */
107     NULL,                        /* get_target_gid */
108 
109     NULL,                        /* input_filter */
110 
111     NULL,                        /* ini_defaults */
112     0,                           /* phpinfo_as_text */
113 
114     NULL,                        /* ini_entries */
115     NULL,                        /* additional_functions */
116     NULL                         /* input_filter_init */
117 };
118 
119 typedef struct {
120     nxt_task_t           *task;
121     nxt_app_rmsg_t       *rmsg;
122     nxt_app_request_t    r;
123     nxt_str_t            script;
124     nxt_app_wmsg_t       *wmsg;
125     nxt_mp_t             *mem_pool;
126 
127     size_t               body_preread_size;
128 } nxt_php_run_ctx_t;
129 
130 nxt_inline nxt_int_t nxt_php_write(nxt_php_run_ctx_t *ctx,
131                       const u_char *data, size_t len,
132                       nxt_bool_t flush, nxt_bool_t last);
133 
134 
135 static nxt_str_t nxt_php_path;
136 static nxt_str_t nxt_php_root;
137 static nxt_str_t nxt_php_script;
138 static nxt_str_t nxt_php_index = nxt_string("index.php");
139 
140 static void
141 nxt_php_strdup(nxt_str_t *dst, nxt_str_t *src)
142 {
143     dst->start = malloc(src->length + 1);
144     nxt_memcpy(dst->start, src->start, src->length);
145     dst->start[src->length] = '\0';
146 
147     dst->length = src->length;
148 }
149 
150 static void
151 nxt_php_str_trim_trail(nxt_str_t *str, u_char t)
152 {
153     while (str->length > 0 && str->start[str->length - 1] == t) {
154         str->length--;
155     }
156 
157     str->start[str->length] = '\0';
158 }
159 
160 
161 static void
162 nxt_php_str_trim_lead(nxt_str_t *str, u_char t)
163 {
164     while (str->length > 0 && str->start[0] == t) {
165         str->length--;
166         str->start++;
167     }
168 }
169 
170 
171 NXT_EXPORT nxt_application_module_t  nxt_app_module = {
172     nxt_string("php"),
173     nxt_string(PHP_VERSION),
174     nxt_php_init,
175     nxt_php_run,
176 };
177 
178 
179 static nxt_int_t
180 nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
181 {
182     nxt_str_t           *root, *path, *script, *index;
183     nxt_php_app_conf_t  *c;
184 
185     c = &conf->u.php;
186 
187     if (c->root.length == 0) {
188         nxt_log_emerg(task->log, "php root is empty");
189         return NXT_ERROR;
190     }
191 
192     root = &nxt_php_root;
193     path = &nxt_php_path;
194     script = &nxt_php_script;
195     index = &nxt_php_index;
196 
197     nxt_php_strdup(root, &c->root);
198 
199     nxt_php_str_trim_trail(root, '/');
200 
201     if (c->script.length > 0) {
202         nxt_php_str_trim_lead(&c->script, '/');
203 
204         path->length = root->length + c->script.length + 1;
205         path->start = malloc(path->length + 1);
206 
207         nxt_memcpy(path->start, root->start, root->length);
208         path->start[root->length] = '/';
209 
210         nxt_memcpy(path->start + root->length + 1,
211                    c->script.start, c->script.length);
212 
213         path->start[path->length] = '\0';
214 
215 
216         script->length = c->script.length + 1;
217         script->start = malloc(script->length + 1);
218         script->start[0] = '/';
219         nxt_memcpy(script->start + 1, c->script.start, c->script.length);
220         script->start[script->length] = '\0';
221 
222         nxt_log_error(NXT_LOG_INFO, task->log,
223                       "(ABS_MODE) php script \"%V\" root: \"%V\"",
224                       script, root);
225     } else {
226         nxt_log_error(NXT_LOG_INFO, task->log,
227                       "(non ABS_MODE) php root: \"%V\"", root);
228     }
229 
230     if (c->index.length > 0) {
231         nxt_php_strdup(index, &c->index);
232     }
233 
234     sapi_startup(&nxt_php_sapi_module);
235     nxt_php_startup(&nxt_php_sapi_module);
236 
237     return NXT_OK;
238 }
239 
240 
241 static nxt_int_t
242 nxt_php_read_request(nxt_task_t *task, nxt_app_rmsg_t *rmsg,
243     nxt_php_run_ctx_t *ctx)
244 {
245     u_char                    *p;
246     size_t                    s;
247     nxt_int_t                 rc;
248     nxt_str_t                 script_name;
249     nxt_app_request_header_t  *h;
250 
251     h = &ctx->r.header;
252 
253 #define RC(S)                                                                 \
254     do {                                                                      \
255         rc = (S);                                                             \
256         if (nxt_slow_path(rc != NXT_OK)) {                                    \
257             goto fail;                                                        \
258         }                                                                     \
259     } while(0)
260 
261 #define NXT_READ(dst)                                                         \
262     RC(nxt_app_msg_read_str(task, rmsg, (dst)))
263 
264     NXT_READ(&h->method);
265     NXT_READ(&h->target);
266     NXT_READ(&h->path);
267 
268     RC(nxt_app_msg_read_size(task, rmsg, &s));
269     if (s > 0) {
270         s--;
271         h->query.start = h->target.start + s;
272         h->query.length = h->target.length - s;
273 
274         if (h->path.start == NULL) {
275             h->path.start = h->target.start;
276             h->path.length = s - 1;
277         }
278     }
279 
280     if (h->path.start == NULL) {
281         h->path = h->target;
282     }
283 
284     if (nxt_php_path.start == NULL) {
285         if (h->path.start[h->path.length - 1] == '/') {
286             script_name = nxt_php_index;
287         } else {
288             script_name.length = 0;
289         }
290 
291         ctx->script.length = nxt_php_root.length + h->path.length +
292                              script_name.length;
293         ctx->script.start = nxt_mp_nget(ctx->mem_pool,
294             ctx->script.length + 1);
295 
296         p = ctx->script.start;
297 
298         nxt_memcpy(p, nxt_php_root.start, nxt_php_root.length);
299         p += nxt_php_root.length;
300 
301         nxt_memcpy(p, h->path.start, h->path.length);
302         p += h->path.length;
303 
304         if (script_name.length > 0) {
305             nxt_memcpy(p, script_name.start, script_name.length);
306             p += script_name.length;
307         }
308 
309         p[0] = '\0';
310     } else {
311         ctx->script = nxt_php_path;
312     }
313 
314     NXT_READ(&h->version);
315 
316     NXT_READ(&ctx->r.remote);
317 
318     NXT_READ(&h->host);
319     NXT_READ(&h->cookie);
320     NXT_READ(&h->content_type);
321     NXT_READ(&h->content_length);
322 
323     RC(nxt_app_msg_read_size(task, rmsg, &s));
324     h->parsed_content_length = s;
325 
326 #undef NXT_READ
327 #undef RC
328 
329     /* Further headers read moved to nxt_php_register_variables. */
330     return NXT_OK;
331 
332 fail:
333 
334     return rc;
335 }
336 
337 
338 static nxt_int_t
339 nxt_php_run(nxt_task_t *task,
340     nxt_app_rmsg_t *rmsg, nxt_app_wmsg_t *wmsg)
341 {
342     nxt_int_t                 rc;
343     zend_file_handle          file_handle;
344     nxt_php_run_ctx_t         run_ctx;
345     nxt_app_request_header_t  *h;
346 
347     if (nxt_php_root.length == 0) {
348         return NXT_ERROR;
349     }
350 
351     nxt_memzero(&run_ctx, sizeof(run_ctx));
352 
353     run_ctx.task = task;
354     run_ctx.rmsg = rmsg;
355     run_ctx.wmsg = wmsg;
356 
357     run_ctx.mem_pool = nxt_mp_create(1024, 128, 256, 32);
358 
359     h = &run_ctx.r.header;
360 
361     rc = nxt_php_read_request(task, rmsg, &run_ctx);
362 
363     if (nxt_slow_path(rc != NXT_OK)) {
364         goto fail;
365     }
366 
367     SG(server_context) = &run_ctx;
368     SG(request_info).request_uri = (char *) h->target.start;
369     SG(request_info).request_method = (char *) h->method.start;
370 
371     SG(request_info).proto_num = 1001;
372 
373     SG(request_info).query_string = (char *) h->query.start;
374     SG(request_info).content_length = h->parsed_content_length;
375 
376     if (h->content_type.start != NULL) {
377         SG(request_info).content_type = (char *) h->content_type.start;
378     }
379 
380     SG(sapi_headers).http_response_code = 200;
381 
382     SG(request_info).path_translated = NULL;
383 
384     file_handle.type = ZEND_HANDLE_FILENAME;
385     file_handle.filename = (char *) run_ctx.script.start;
386     file_handle.free_filename = 0;
387     file_handle.opened_path = NULL;
388 
389     nxt_debug(task, "handle.filename = '%s'", run_ctx.script.start);
390 
391     if (nxt_php_path.start != NULL) {
392         nxt_debug(task, "run script %V in absolute mode", &nxt_php_path);
393     } else {
394         nxt_debug(task, "run script %V", &run_ctx.script);
395     }
396 
397     if (nxt_slow_path(php_request_startup() == FAILURE)) {
398         nxt_debug(task, "php_request_startup() failed");
399         goto fail;
400     }
401 
402     php_execute_script(&file_handle TSRMLS_CC);
403     php_request_shutdown(NULL);
404 
405     nxt_app_msg_flush(task, wmsg, 1);
406 
407     nxt_mp_destroy(run_ctx.mem_pool);
408 
409     return NXT_OK;
410 
411 fail:
412 
413     nxt_mp_destroy(run_ctx.mem_pool);
414 
415     return NXT_ERROR;
416 }
417 
418 
419 nxt_inline nxt_int_t
420 nxt_php_write(nxt_php_run_ctx_t *ctx, const u_char *data, size_t len,
421     nxt_bool_t flush, nxt_bool_t last)
422 {
423     nxt_int_t  rc;
424 
425     if (len > 0) {
426         rc = nxt_app_msg_write_raw(ctx->task, ctx->wmsg, data, len);
427     } else {
428         rc = NXT_OK;
429     }
430 
431     if (flush || last) {
432         rc = nxt_app_msg_flush(ctx->task, ctx->wmsg, last);
433     }
434 
435     return rc;
436 }
437 
438 
439 static int
440 nxt_php_startup(sapi_module_struct *sapi_module)
441 {
442    return php_module_startup(sapi_module, NULL, 0);
443 }
444 
445 
446 #ifdef NXT_PHP7
447 static size_t
448 nxt_php_unbuffered_write(const char *str, size_t str_length TSRMLS_DC)
449 #else
450 static int
451 nxt_php_unbuffered_write(const char *str, uint str_length TSRMLS_DC)
452 #endif
453 {
454     nxt_int_t          rc;
455     nxt_php_run_ctx_t  *ctx;
456 
457     ctx = SG(server_context);
458 
459     rc = nxt_php_write(ctx, (u_char *) str, str_length, 1, 0);
460 
461     if (nxt_fast_path(rc == NXT_OK)) {
462         return str_length;
463     }
464 
465     // TODO handle NXT_AGAIN
466     php_handle_aborted_connection();
467     return 0;
468 }
469 
470 
471 static void
472 nxt_php_flush(void *server_context)
473 {
474     nxt_php_run_ctx_t  *ctx;
475 
476     ctx = server_context;
477 
478     (void) nxt_app_msg_flush(ctx->task, ctx->wmsg, 0);
479 }
480 
481 
482 static int
483 nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
484 {
485     size_t               len;
486     u_char               *status, buf[64];
487     nxt_int_t            rc;
488     nxt_php_run_ctx_t    *ctx;
489     sapi_header_struct   *h;
490     zend_llist_position  zpos;
491 
492     static const u_char default_repsonse[]
493         = "HTTP/1.1 200 OK\r\n"
494           "Server: nginext/0.1\r\n"
495           "Content-Type: text/html; charset=UTF-8\r\n"
496           "Connection: close\r\n"
497           "\r\n";
498 
499     static const u_char default_headers[]
500         = "Server: nginext/0.1\r\n"
501           "Connection: close\r\n";
502 
503     static const u_char http_11[] = "HTTP/1.1 ";
504     static const u_char cr_lf[] = "\r\n";
505     static const u_char _200_ok[] = "200 OK";
506 
507     ctx = SG(server_context);
508 
509 #define RC(S)                                                                 \
510     do {                                                                      \
511         rc = (S);                                                             \
512         if (nxt_slow_path(rc != NXT_OK)) {                                    \
513             goto fail;                                                        \
514         }                                                                     \
515     } while(0)
516 
517     if (SG(request_info).no_headers == 1) {
518         RC(nxt_php_write(ctx, default_repsonse, sizeof(default_repsonse) - 1,
519                       1, 0));
520         return SAPI_HEADER_SENT_SUCCESSFULLY;
521     }
522 
523     if (SG(sapi_headers).http_status_line) {
524         status = (u_char *) SG(sapi_headers).http_status_line;
525         len = nxt_strlen(status);
526 
527         RC(nxt_php_write(ctx, status, len, 0, 0));
528 
529     } else if (SG(sapi_headers).http_response_code) {
530         status = nxt_sprintf(buf, buf + sizeof(buf), "%03d",
531                         SG(sapi_headers).http_response_code);
532         len = status - buf;
533 
534         RC(nxt_php_write(ctx, http_11, sizeof(http_11) - 1, 0, 0));
535         RC(nxt_php_write(ctx, buf, len, 0, 0));
536 
537     } else {
538         RC(nxt_php_write(ctx, http_11, sizeof(http_11) - 1, 0, 0));
539         RC(nxt_php_write(ctx, _200_ok, sizeof(_200_ok) - 1, 0, 0));
540     }
541 
542     RC(nxt_php_write(ctx, cr_lf, sizeof(cr_lf) - 1, 0, 0));
543     RC(nxt_php_write(ctx, default_headers, sizeof(default_headers) - 1, 0, 0));
544 
545     h = zend_llist_get_first_ex(&sapi_headers->headers, &zpos);
546 
547     while (h) {
548         RC(nxt_php_write(ctx, (u_char *) h->header, h->header_len, 0, 0));
549         RC(nxt_php_write(ctx, cr_lf, sizeof(cr_lf) - 1, 0, 0));
550 
551         h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos);
552     }
553 
554     RC(nxt_php_write(ctx, cr_lf, sizeof(cr_lf) - 1, 1, 0));
555 
556 #undef RC
557 
558     return SAPI_HEADER_SENT_SUCCESSFULLY;
559 
560 fail:
561 
562     // TODO handle NXT_AGAIN
563     return SAPI_HEADER_SEND_FAILED;
564 }
565 
566 
567 #ifdef NXT_PHP7
568 static size_t
569 nxt_php_read_post(char *buffer, size_t count_bytes TSRMLS_DC)
570 #else
571 static int
572 nxt_php_read_post(char *buffer, uint count_bytes TSRMLS_DC)
573 #endif
574 {
575     size_t                    size, rest;
576     nxt_php_run_ctx_t         *ctx;
577     nxt_app_request_header_t  *h;
578 
579     ctx = SG(server_context);
580     h = &ctx->r.header;
581 
582     rest = (size_t) h->parsed_content_length - SG(read_post_bytes);
583 
584     nxt_debug(ctx->task, "nxt_php_read_post %O", rest);
585 
586     if (rest == 0) {
587         return 0;
588     }
589 
590     rest = nxt_min(ctx->body_preread_size, (size_t) count_bytes);
591     size = nxt_app_msg_read_raw(ctx->task, ctx->rmsg, buffer, rest);
592 
593     ctx->body_preread_size -= size;
594 
595     return size;
596 }
597 
598 
599 static char *
600 nxt_php_read_cookies(TSRMLS_D)
601 {
602     nxt_php_run_ctx_t  *ctx;
603 
604     ctx = SG(server_context);
605 
606     nxt_debug(ctx->task, "nxt_php_read_cookies");
607 
608     return (char *) ctx->r.header.cookie.start;
609 }
610 
611 
612 static void
613 nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
614 {
615     u_char                    *colon;
616     nxt_str_t                 n, v;
617     nxt_int_t                 rc;
618     nxt_str_t                 host, server_name, server_port;
619     nxt_task_t                *task;
620     nxt_php_run_ctx_t         *ctx;
621     nxt_app_request_header_t  *h;
622 
623     static nxt_str_t def_host = nxt_string("localhost");
624     static nxt_str_t def_port = nxt_string("80");
625 
626     ctx = SG(server_context);
627 
628     h = &ctx->r.header;
629     task = ctx->task;
630 
631     nxt_debug(task, "php register variables");
632 
633 #define NXT_PHP_SET(n, v)                                                     \
634     nxt_debug(task, "php: register %s='%V'", n, &v);                          \
635     php_register_variable_safe((char *) (n), (char *) (v).start,              \
636                                (v).length, track_vars_array TSRMLS_CC)        \
637 
638     NXT_PHP_SET("SERVER_PROTOCOL", h->version);
639 
640 /*
641  * 'SCRIPT_NAME'
642  * Contains the current script's path. This is useful for pages which need to
643  * point to themselves. The __FILE__ constant contains the full path and
644  * filename of the current (i.e. included) file.
645  */
646 
647 /*
648  * 'SCRIPT_FILENAME'
649  * The absolute pathname of the currently executing script.
650  */
651 
652 /*
653  * 'DOCUMENT_ROOT'
654  * The document root directory under which the current script is executing,
655  * as defined in the server's configuration file.
656  */
657 
658     if (nxt_php_script.start != NULL) {
659     // ABS_MODE
660 /*
661  * 'PHP_SELF'
662  * The filename of the currently executing script, relative to the document
663  * root. For instance, $_SERVER['PHP_SELF'] in a script at the address
664  * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__ constant
665  * contains the full path and filename of the current (i.e. included) file.
666  * If PHP is running as a command-line processor this variable contains the
667  * script name since PHP 4.3.0. Previously it was not available.
668  */
669         NXT_PHP_SET("PHP_SELF", nxt_php_script);
670         NXT_PHP_SET("SCRIPT_NAME", nxt_php_script);
671     } else {
672         NXT_PHP_SET("PHP_SELF", h->path);
673         NXT_PHP_SET("SCRIPT_NAME", h->path);
674     }
675 
676     NXT_PHP_SET("SCRIPT_FILENAME", ctx->script);
677     NXT_PHP_SET("DOCUMENT_ROOT", nxt_php_root);
678 
679     NXT_PHP_SET("REQUEST_METHOD", h->method);
680     NXT_PHP_SET("REQUEST_URI", h->target);
681 
682     if (h->query.start != NULL) {
683         NXT_PHP_SET("QUERY_STRING", h->query);
684     }
685 
686     if (h->content_type.start != NULL) {
687         NXT_PHP_SET("CONTENT_TYPE", h->content_type);
688     }
689 
690     if (h->content_length.start != NULL) {
691         NXT_PHP_SET("CONTENT_LENGTH", h->content_length);
692     }
693 
694     host = h->host;
695     if (host.length == 0) {
696         host = def_host;
697     }
698 
699     server_name = host;
700     colon = nxt_memchr(host.start, ':', host.length);
701 
702     if (colon != NULL) {
703         server_name.length = colon - host.start;
704 
705         server_port.start = colon + 1;
706         server_port.length = host.length - server_name.length - 1;
707     } else {
708         server_port = def_port;
709     }
710 
711     NXT_PHP_SET("SERVER_NAME", server_name);
712     NXT_PHP_SET("SERVER_PORT", server_port);
713 
714     NXT_PHP_SET("REMOTE_ADDR", ctx->r.remote);
715 
716     while (nxt_app_msg_read_str(task, ctx->rmsg, &n) == NXT_OK) {
717         if (nxt_slow_path(n.length == 0)) {
718             break;
719         }
720 
721         rc = nxt_app_msg_read_str(task, ctx->rmsg, &v);
722         if (nxt_slow_path(rc != NXT_OK)) {
723             break;
724         }
725 
726         NXT_PHP_SET(n.start, v);
727     }
728 
729     nxt_app_msg_read_size(task, ctx->rmsg, &ctx->body_preread_size);
730 
731 #undef NXT_PHP_SET
732 }
733 
734 
735 static void
736 nxt_php_log_message(char *message
737 #ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
738                                 , int syslog_type_int
739 #endif
740 )
741 {
742     return;
743 }
744