add embeded http library so we can test remote scenarios
[platform/upstream/libzypp.git] / vendor / shttpd / compat_win32.c
1 /*
2  * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3  * All rights reserved
4  *
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * Sergey Lyubka wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.
9  */
10
11 #include "defs.h"
12
13 static SERVICE_STATUS           ss; 
14 static SERVICE_STATUS_HANDLE    hStatus; 
15 static SERVICE_DESCRIPTION      service_descr = {"Web server"};
16
17 static void
18 fix_directory_separators(char *path)
19 {
20         for (; *path != '\0'; path++) {
21                 if (*path == '/')
22                         *path = '\\';
23                 if (*path == '\\')
24                         while (path[1] == '\\' || path[1] == '/') 
25                                 (void) memmove(path + 1,
26                                     path + 2, strlen(path + 2) + 1);
27         }
28 }
29
30 static int
31 protect_against_code_disclosure(const wchar_t *path)
32 {
33         WIN32_FIND_DATAW        data;
34         HANDLE                  handle;
35         const wchar_t           *p;
36
37         /*
38          * Protect against CGI code disclosure under Windows.
39          * This is very nasty hole. Windows happily opens files with
40          * some garbage in the end of file name. So fopen("a.cgi    ", "r")
41          * actually opens "a.cgi", and does not return an error! And since
42          * "a.cgi    " does not have valid CGI extension, this leads to
43          * the CGI code disclosure.
44          * To protect, here we delete all fishy characters from the
45          * end of file name.
46          */
47
48         if ((handle = FindFirstFileW(path, &data)) == INVALID_HANDLE_VALUE)
49                 return (FALSE);
50
51         FindClose(handle);
52
53         for (p = path + wcslen(path); p > path && p[-1] != L'\\';)
54                 p--;
55         
56         if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
57             wcscmp(data.cFileName, p) != 0)
58                 return (FALSE);
59
60         return (TRUE);
61 }
62
63 int
64 _shttpd_open(const char *path, int flags, int mode)
65 {
66         char    buf[FILENAME_MAX];
67         wchar_t wbuf[FILENAME_MAX];
68
69         _shttpd_strlcpy(buf, path, sizeof(buf));
70         fix_directory_separators(buf);
71         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
72
73         if (protect_against_code_disclosure(wbuf) == FALSE)
74                 return (-1);
75
76         return (_wopen(wbuf, flags));
77 }
78
79 int
80 _shttpd_stat(const char *path, struct stat *stp)
81 {
82         char    buf[FILENAME_MAX], *p;
83         wchar_t wbuf[FILENAME_MAX];
84
85         _shttpd_strlcpy(buf, path, sizeof(buf));
86         fix_directory_separators(buf);
87
88         p = buf + strlen(buf) - 1;
89         while (p > buf && *p == '\\' && p[-1] != ':')
90                 *p-- = '\0';
91
92         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
93
94         return (_wstat(wbuf, (struct _stat *) stp));
95 }
96
97 int
98 _shttpd_remove(const char *path)
99 {
100         char    buf[FILENAME_MAX];
101         wchar_t wbuf[FILENAME_MAX];
102
103         _shttpd_strlcpy(buf, path, sizeof(buf));
104         fix_directory_separators(buf);
105
106         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
107
108         return (_wremove(wbuf));
109 }
110
111 int
112 _shttpd_rename(const char *path1, const char *path2)
113 {
114         char    buf1[FILENAME_MAX];
115         char    buf2[FILENAME_MAX];
116         wchar_t wbuf1[FILENAME_MAX];
117         wchar_t wbuf2[FILENAME_MAX];
118
119         _shttpd_strlcpy(buf1, path1, sizeof(buf1));
120         _shttpd_strlcpy(buf2, path2, sizeof(buf2));
121         fix_directory_separators(buf1);
122         fix_directory_separators(buf2);
123
124         MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1));
125         MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2));
126
127         return (_wrename(wbuf1, wbuf2));
128 }
129
130 int
131 _shttpd_mkdir(const char *path, int mode)
132 {
133         char    buf[FILENAME_MAX];
134         wchar_t wbuf[FILENAME_MAX];
135
136         _shttpd_strlcpy(buf, path, sizeof(buf));
137         fix_directory_separators(buf);
138
139         MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
140
141         return (_wmkdir(wbuf));
142 }
143
144 static char *
145 wide_to_utf8(const wchar_t *str)
146 {
147         char *buf = NULL;
148         if (str) {
149                 int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
150                 if (nchar > 0) {
151                         buf = malloc(nchar);
152                         if (!buf)
153                                 errno = ENOMEM;
154                         else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) {
155                                 free(buf);
156                                 buf = NULL;
157                                 errno = EINVAL;
158                         }
159                 } else
160                         errno = EINVAL;
161         } else
162                 errno = EINVAL;
163         return buf;
164 }
165
166 char *
167 _shttpd_getcwd(char *buffer, int maxlen)
168 {
169         char *result = NULL;
170         wchar_t *wbuffer, *wresult;
171
172         if (buffer) {
173                 /* User-supplied buffer */
174                 wbuffer = malloc(maxlen * sizeof(wchar_t));
175                 if (wbuffer == NULL)
176                         return NULL;
177         } else
178                 /* Dynamically allocated buffer */
179                 wbuffer = NULL;
180         wresult = _wgetcwd(wbuffer, maxlen);
181         if (wresult) {
182                 int err = errno;
183                 if (buffer) {
184                         /* User-supplied buffer */
185                         int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL);
186                         if (n == 0)
187                                 err = ERANGE;
188                         free(wbuffer);
189                         result = buffer;
190                 } else {
191                         /* Buffer allocated by _wgetcwd() */
192                         result = wide_to_utf8(wresult);
193                         err = errno;
194                         free(wresult);
195                 }
196                 errno = err;
197         }
198         return result;
199 }
200
201 DIR *
202 opendir(const char *name)
203 {
204         DIR             *dir = NULL;
205         char            path[FILENAME_MAX];
206         wchar_t         wpath[FILENAME_MAX];
207
208         if (name == NULL || name[0] == '\0') {
209                 errno = EINVAL;
210         } else if ((dir = malloc(sizeof(*dir))) == NULL) {
211                 errno = ENOMEM;
212         } else {
213                 _shttpd_snprintf(path, sizeof(path), "%s/*", name);
214                 fix_directory_separators(path);
215                 MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath));
216                 dir->handle = FindFirstFileW(wpath, &dir->info);
217
218                 if (dir->handle != INVALID_HANDLE_VALUE) {
219                         dir->result.d_name[0] = '\0';
220                 } else {
221                         free(dir);
222                         dir = NULL;
223                 }
224         }
225
226         return (dir);
227 }
228
229 int
230 closedir(DIR *dir)
231 {
232         int result = -1;
233
234         if (dir != NULL) {
235                 if (dir->handle != INVALID_HANDLE_VALUE)
236                         result = FindClose(dir->handle) ? 0 : -1;
237
238                 free(dir);
239         }
240
241         if (result == -1) 
242                 errno = EBADF;
243
244         return (result);
245 }
246
247 struct dirent *
248 readdir(DIR *dir)
249 {
250         struct dirent *result = 0;
251
252         if (dir && dir->handle != INVALID_HANDLE_VALUE) {
253                 if(!dir->result.d_name ||
254                     FindNextFileW(dir->handle, &dir->info)) {
255                         result = &dir->result;
256
257                         WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName,
258                             -1, result->d_name,
259                             sizeof(result->d_name), NULL, NULL);
260                 }
261         } else {
262                 errno = EBADF;
263         }
264
265         return (result);
266 }
267
268 int
269 _shttpd_set_non_blocking_mode(int fd)
270 {
271         unsigned long   on = 1;
272
273         return (ioctlsocket(fd, FIONBIO, &on));
274 }
275
276 void
277 _shttpd_set_close_on_exec(int fd)
278 {
279         fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */
280 }
281
282 #if !defined(NO_CGI)
283
284 struct threadparam {
285         SOCKET  s;
286         HANDLE  hPipe;
287         big_int_t content_len;
288 };
289
290
291 enum ready_mode_t {IS_READY_FOR_READ, IS_READY_FOR_WRITE};
292
293 /*
294  * Wait until given socket is in ready state. Always return TRUE.
295  */
296 static int
297 is_socket_ready(int sock, enum ready_mode_t mode)
298 {
299         fd_set          read_set, write_set;
300
301         FD_ZERO(&read_set);
302         FD_ZERO(&write_set);
303
304         if (mode == IS_READY_FOR_READ)
305                 FD_SET(sock, &read_set);
306         else
307                 FD_SET(sock, &write_set);
308
309         select(sock + 1, &read_set, &write_set, NULL, NULL);
310
311         return (TRUE);
312 }
313
314 /*
315  * Thread function that reads POST data from the socket pair
316  * and writes it to the CGI process.
317  */
318 static void//DWORD WINAPI
319 stdoutput(void *arg)
320 {
321         struct threadparam      *tp = arg;
322         int                     n, sent, stop = 0;
323         big_int_t               total = 0;
324         DWORD k;
325         char                    buf[BUFSIZ];
326         size_t                  max_recv;
327
328         max_recv = min(sizeof(buf), tp->content_len - total);
329         while (!stop &&
330             max_recv > 0 &&
331             is_socket_ready(tp->s, IS_READY_FOR_READ) &&
332             (n = recv(tp->s, buf, max_recv, 0)) > 0) {
333                 if (n == -1 && ERRNO == EWOULDBLOCK)
334                         continue;
335                 for (sent = 0; !stop && sent < n; sent += k)
336                         if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0))
337                                 stop++;
338                 total += n;
339                 max_recv = min(sizeof(buf), tp->content_len - total);
340         }
341         
342         CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */
343         free(tp);
344 }
345
346 /*
347  * Thread function that reads CGI output and pushes it to the socket pair.
348  */
349 static void
350 stdinput(void *arg)
351 {
352         struct threadparam      *tp = arg;
353         static                  int ntotal;
354         int                     k, stop = 0;
355         DWORD n, sent;
356         char                    buf[BUFSIZ];
357
358         while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
359                 ntotal += n;
360                 for (sent = 0; !stop && sent < n; sent += k) {
361                         if (is_socket_ready(tp->s, IS_READY_FOR_WRITE) &&
362                             (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) {
363                                 if (k == -1 && ERRNO == EWOULDBLOCK) {
364                                         k = 0;
365                                         continue;
366                                 }
367                                 stop++;
368                         }
369                 }
370         }
371         CloseHandle(tp->hPipe);
372         
373         /*
374          * Windows is a piece of crap. When this thread closes its end
375          * of the socket pair, the other end (get_cgi() function) may loose
376          * some data. I presume, this happens if get_cgi() is not fast enough,
377          * and the data written by this end does not "push-ed" to the other
378          * end socket buffer. So after closesocket() the remaining data is
379          * gone. If I put shutdown() before closesocket(), that seems to
380          * fix the problem, but I am not sure this is the right fix.
381          * XXX (submitted by James Marshall) we do not do shutdown() on UNIX.
382          * If fork() is called from user callback, shutdown() messes up things.
383          */
384         shutdown(tp->s, 2);
385
386         closesocket(tp->s);
387         free(tp);
388
389         _endthread();
390 }
391
392 static void
393 spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *),
394                 big_int_t content_len)
395 {
396         struct threadparam      *tp;
397         DWORD                   tid;
398
399         tp = malloc(sizeof(*tp));
400         assert(tp != NULL);
401
402         tp->s           = sock;
403         tp->hPipe       = hPipe;
404         tp->content_len = content_len;
405         _beginthread(func, 0, tp);
406 }
407
408 int
409 _shttpd_spawn_process(struct conn *c, const char *prog, char *envblk,
410                 char *envp[], int sock, const char *dir)
411 {
412         HANDLE  a[2], b[2], h[2], me;
413         DWORD   flags;
414         char    *p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX];
415         FILE    *fp;
416         STARTUPINFOA            si;
417         PROCESS_INFORMATION     pi;
418
419         me = GetCurrentProcess();
420         flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
421
422         /* FIXME add error checking code here */
423         CreatePipe(&a[0], &a[1], NULL, 0);
424         CreatePipe(&b[0], &b[1], NULL, 0);
425         DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags);
426         DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags);
427         
428         (void) memset(&si, 0, sizeof(si));
429         (void) memset(&pi, 0, sizeof(pi));
430
431         /* XXX redirect CGI errors to the error log file */
432         si.cb           = sizeof(si);
433         si.dwFlags      = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
434         si.wShowWindow  = SW_HIDE;
435         si.hStdOutput   = h[1];
436         si.hStdInput    = h[0];
437
438         /* If CGI file is a script, try to read the interpreter line */
439         interp = c->ctx->options[OPT_CGI_INTERPRETER];
440         if (interp == NULL) {
441                 if ((fp = fopen(prog, "r")) != NULL) {
442                         (void) fgets(line, sizeof(line), fp);
443                         if (memcmp(line, "#!", 2) != 0)
444                                 line[2] = '\0';
445                         /* Trim whitespaces from interpreter name */
446                         for (p = &line[strlen(line) - 1]; p > line &&
447                             isspace(*p); p--)
448                                 *p = '\0';
449                         (void) fclose(fp);
450                 }
451                 interp = line + 2;
452                 (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s%s%s",
453                     line + 2, line[2] == '\0' ? "" : " ", prog);
454         }
455
456         if ((p = strrchr(prog, '/')) != NULL)
457                 prog = p + 1;
458
459         (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s %s", interp, prog);
460
461         (void) _shttpd_snprintf(line, sizeof(line), "%s", dir);
462         fix_directory_separators(line);
463         fix_directory_separators(cmdline);
464
465         /*
466          * Spawn reader & writer threads before we create CGI process.
467          * Otherwise CGI process may die too quickly, loosing the data
468          */
469         spawn_stdio_thread(sock, b[0], stdinput, 0);
470         spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len);
471
472         if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
473             CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
474                 _shttpd_elog(E_LOG, c,
475                     "redirect: CreateProcess(%s): %d", cmdline, ERRNO);
476                 return (-1);
477         } else {
478                 CloseHandle(h[0]);
479                 CloseHandle(h[1]);
480                 CloseHandle(pi.hThread);
481                 CloseHandle(pi.hProcess);
482         }
483
484         return (0);
485 }
486
487 #endif /* !NO_CGI */
488
489 #define ID_TRAYICON     100
490 #define ID_QUIT         101
491 static NOTIFYICONDATA   ni;
492
493 static LRESULT CALLBACK
494 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
495 {
496         POINT   pt;
497         HMENU   hMenu;   
498
499         switch (msg) {
500         case WM_COMMAND:
501                 switch (LOWORD(wParam)) {
502                 case ID_QUIT:
503                         exit(EXIT_SUCCESS);
504                         break;
505                 }
506                 break;
507         case WM_USER:
508                 switch (lParam) {
509                 case WM_RBUTTONUP:
510                 case WM_LBUTTONUP:
511                 case WM_LBUTTONDBLCLK:
512                         hMenu = CreatePopupMenu();
513                         AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD");
514                         GetCursorPos(&pt);
515                         TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
516                         DestroyMenu(hMenu);
517                         break;
518                 }
519                 break;
520         }
521
522         return (DefWindowProc(hWnd, msg, wParam, lParam));
523 }
524
525 static void
526 systray(void *arg)
527 {
528         WNDCLASS        cls;
529         HWND            hWnd;
530         MSG             msg;
531
532         (void) memset(&cls, 0, sizeof(cls));
533
534         cls.lpfnWndProc = (WNDPROC) WindowProc; 
535         cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
536         cls.lpszClassName = "shttpd v." VERSION; 
537
538         if (!RegisterClass(&cls)) 
539                 _shttpd_elog(E_FATAL, NULL, "RegisterClass: %d", ERRNO);
540         else if ((hWnd = CreateWindow(cls.lpszClassName, "",
541             WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, NULL, arg)) == NULL)
542                 _shttpd_elog(E_FATAL, NULL, "CreateWindow: %d", ERRNO);
543         ShowWindow(hWnd, SW_HIDE);
544         
545         ni.cbSize = sizeof(ni);
546         ni.uID = ID_TRAYICON;
547         ni.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
548         ni.hIcon = LoadIcon(NULL, IDI_APPLICATION);
549         ni.hWnd = hWnd;
550         _shttpd_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server");
551         ni.uCallbackMessage = WM_USER;
552         Shell_NotifyIcon(NIM_ADD, &ni);
553
554         while (GetMessage(&msg, hWnd, 0, 0)) { 
555                 TranslateMessage(&msg); 
556                 DispatchMessage(&msg); 
557         }
558 }
559
560 int
561 _shttpd_set_systray(struct shttpd_ctx *ctx, const char *opt)
562 {
563         HWND            hWnd;
564         char            title[512];
565         static WNDPROC  oldproc;
566
567         if (!_shttpd_is_true(opt))
568                 return (TRUE);
569
570         FreeConsole();
571         GetConsoleTitle(title, sizeof(title));
572         hWnd = FindWindow(NULL, title);
573         ShowWindow(hWnd, SW_HIDE);
574         _beginthread(systray, 0, hWnd);
575
576         return (TRUE);
577 }
578
579 int
580 _shttpd_set_nt_service(struct shttpd_ctx *ctx, const char *action)
581 {
582         SC_HANDLE       hSCM, hService;
583         char            path[FILENAME_MAX], key[128];
584         HKEY            hKey;
585         DWORD           dwData;
586
587
588         if (!strcmp(action, "install")) {
589                 if ((hSCM = OpenSCManager(NULL, NULL,
590                     SC_MANAGER_ALL_ACCESS)) == NULL)
591                         _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO);
592
593                 GetModuleFileName(NULL, path, sizeof(path));
594
595                 hService = CreateService(hSCM, SERVICE_NAME, SERVICE_NAME,
596                     SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
597                     SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path,
598                     NULL, NULL, NULL, NULL, NULL);
599
600                 if (!hService)
601                         _shttpd_elog(E_FATAL, NULL,
602                             "Error installing service (%d)", ERRNO);
603
604                 ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION,
605                     &service_descr);
606                 _shttpd_elog(E_FATAL, NULL, "Service successfully installed");
607
608
609         } else if (!strcmp(action, "uninstall")) {
610
611                 if ((hSCM = OpenSCManager(NULL, NULL,
612                     SC_MANAGER_ALL_ACCESS)) == NULL) {
613                         _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO);
614                 } else if ((hService = OpenService(hSCM,
615                     SERVICE_NAME, DELETE)) == NULL) {
616                         _shttpd_elog(E_FATAL, NULL,
617                             "Error opening service (%d)", ERRNO);
618                 } else if (!DeleteService(hService)) {
619                         _shttpd_elog(E_FATAL, NULL,
620                             "Error deleting service (%d)", ERRNO);
621                 } else {
622                         _shttpd_elog(E_FATAL, NULL, "Service deleted");
623                 }
624
625         } else {
626                 _shttpd_elog(E_FATAL, NULL, "Use -service <install|uninstall>");
627         }
628
629         /* NOTREACHED */
630         return (TRUE);
631 }
632
633 static void WINAPI
634 ControlHandler(DWORD code) 
635
636         if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
637                 ss.dwWin32ExitCode      = 0; 
638                 ss.dwCurrentState       = SERVICE_STOPPED; 
639         } 
640  
641         SetServiceStatus(hStatus, &ss);
642 }
643
644 static void WINAPI
645 ServiceMain(int argc, char *argv[]) 
646 {
647         char    path[MAX_PATH], *p, *av[] = {"shttpd_service", path, NULL};
648         struct shttpd_ctx       *ctx;
649
650         ss.dwServiceType      = SERVICE_WIN32; 
651         ss.dwCurrentState     = SERVICE_RUNNING; 
652         ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
653
654         hStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler);
655         SetServiceStatus(hStatus, &ss); 
656
657         GetModuleFileName(NULL, path, sizeof(path));
658
659         if ((p = strrchr(path, DIRSEP)) != NULL)
660                 *++p = '\0';
661
662         strcat(path, CONFIG_FILE);      /* woo ! */
663
664         ctx = shttpd_init(NELEMS(av) - 1, av);
665         if ((ctx = shttpd_init(NELEMS(av) - 1, av)) == NULL)
666                 _shttpd_elog(E_FATAL, NULL, "Cannot initialize SHTTP context");
667
668         while (ss.dwCurrentState == SERVICE_RUNNING)
669                 shttpd_poll(ctx, INT_MAX);
670         shttpd_fini(ctx);
671
672         ss.dwCurrentState  = SERVICE_STOPPED; 
673         ss.dwWin32ExitCode = -1; 
674         SetServiceStatus(hStatus, &ss); 
675 }
676
677 void
678 try_to_run_as_nt_service(void)
679 {
680         static SERVICE_TABLE_ENTRY service_table[] = {
681                 {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
682                 {NULL, NULL}
683         };
684
685         if (StartServiceCtrlDispatcher(service_table))
686                 exit(EXIT_SUCCESS);
687 }