]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/chttpget.cpp
ryan's struct patch for gcc 2.95
[taylor/freespace2.git] / src / inetfile / chttpget.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10 * $Logfile: /Freespace2/code/Inetfile/Chttpget.cpp $
11 * $Revision$
12 * $Date$
13 * $Author$
14 *
15 * HTTP Client class (get only)
16 *
17 * $Log$
18 * Revision 1.7  2002/06/17 06:33:09  relnev
19 * ryan's struct patch for gcc 2.95
20 *
21 * Revision 1.6  2002/06/09 04:41:21  relnev
22 * added copyright header
23 *
24 * Revision 1.5  2002/06/02 04:26:34  relnev
25 * warning cleanup
26 *
27 * Revision 1.4  2002/05/26 20:32:24  theoddone33
28 * Fix some minor stuff
29 *
30 * Revision 1.3  2002/05/26 20:20:54  relnev
31 * unix.h: updated
32 *
33 * inetfile/: complete
34 *
35 * Revision 1.2  2002/05/07 03:16:45  theoddone33
36 * The Great Newline Fix
37 *
38 * Revision 1.1.1.1  2002/05/03 03:28:09  root
39 * Initial import.
40 *
41  * 
42  * 5     8/24/99 1:49a Dave
43  * Fixed client-side afterburner stuttering. Added checkbox for no version
44  * checking on PXO join. Made button info passing more friendly between
45  * client and server.
46  * 
47  * 4     8/22/99 1:19p Dave
48  * Fixed up http proxy code. Cleaned up scoring code. Reverse the order in
49  * which d3d cards are detected.
50  * 
51  * 21    8/21/99 6:33p Kevin
52  * Fixed Proxy Stuff
53  * 
54  * 20    8/21/99 6:48a Jeff
55  * Linux port
56  * 
57  * 19    8/20/99 3:01p Kevin
58  * Added support for Proxies (I hope!)
59  * 
60  * 18    8/15/99 6:38p Jeff
61  * fixed compile error
62  * 
63  * 17    8/15/99 6:26p Kevin
64  * 
65  * 16    4/14/99 1:20a Jeff
66  * fixed case mismatched #includes
67  * 
68  * 15    3/03/99 12:28a Nate
69  * sped up something or other when the connection is done
70  * 
71  * 14    2/03/99 4:20p Kevin
72  * Got multiplayer working with .mn3 files, and setup autodownloading
73  * 
74  * 13    1/27/99 5:49p Kevin
75  * 
76  * 12    1/27/99 5:38p Kevin
77  * 
78  * 11    12/30/98 12:15p Kevin
79  * Auto Mission Download system
80  * 
81  * 10    10/12/98 4:59p Kevin
82  * Added delay to thread when cancelled...
83  * 
84  * 9     10/12/98 4:49p Nate
85  * More fixes
86  * 
87  * 8     10/12/98 1:54p Nate
88  * Fixed bug
89  * 
90  * 7     10/12/98 11:30a Kevin
91  * More memory stuff
92  * 
93  * 6     10/08/98 12:59p Nate
94  * fixed cancel
95  * 
96  * 5     10/08/98 9:57a Kevin
97  * made transfer cancellable
98  * 
99  * 4     7/31/98 12:19p Nate
100  * Fixed http abort problem.
101  * 
102  * 3     7/31/98 11:57a Kevin
103  * Added new functions for getting state
104  * 
105  * 2     6/01/98 10:10a Kevin
106  * Added DLL connection interface and auto update DLL
107  * 
108  * 1     5/27/98 9:52a Kevin
109  * 
110  * 1     5/25/98 5:31p Kevin
111  * Initial version
112 *
113 * $NoKeywords: $
114 */
115
116 #ifndef PLAT_UNIX
117 // #define WIN32
118
119 // #ifdef WIN32
120 #include <windows.h>
121 #include <process.h>
122 // #endif
123 #else
124 #include <sys/types.h>
125 #include <sys/socket.h>
126 #include <netinet/in.h>
127 #include <arpa/inet.h>
128 #include <netdb.h>
129 #include <sys/ioctl.h>
130 #include <errno.h>
131 #include <sys/time.h>
132 #include <unistd.h>
133
134 #include "unix.h"
135
136 #endif
137
138 #include <string.h>
139 #include <stdio.h>
140 #include <stdlib.h>
141 #include <ctype.h>
142
143 #include "inetgetfile.h"
144 #include "chttpget.h"
145
146 #ifdef __LINUX__
147
148 inline void Sleep(int millis)
149 {
150         struct timeval tv;
151         tv.tv_sec = 0;
152         tv.tv_usec = millis*1000;
153         select(0,NULL,NULL,NULL,&tv);
154 }
155 #endif
156
157 #define NW_AGHBN_CANCEL         1
158 #define NW_AGHBN_LOOKUP         2
159 #define NW_AGHBN_READ           3
160
161 #ifndef __LINUX__
162 void __cdecl http_gethostbynameworker(void *parm);
163 #else
164 void *http_gethostbynameworker(void *parm);
165 #endif
166
167 int http_Asyncgethostbyname(unsigned int *ip,int command, char *hostname);
168
169 #ifndef __LINUX__
170 void HTTPObjThread( void * obj )
171 #else
172 void *HTTPObjThread( void * obj )
173 #endif
174 {
175         ((ChttpGet *)obj)->WorkerThread();
176         ((ChttpGet *)obj)->m_Aborted = true;
177         //OutputDebugString("http transfer exiting....\n");
178
179         #ifdef __LINUX__
180         return NULL;
181         #endif
182 }
183
184 void ChttpGet::AbortGet()
185 {
186 // #ifdef WIN32
187         OutputDebugString("Aborting....\n");
188 // #endif
189         m_Aborting = true;
190         while(!m_Aborted) Sleep(50); //Wait for the thread to end
191 // #ifdef WIN32
192         OutputDebugString("Aborted....\n");
193 // #endif
194 }
195
196 ChttpGet::ChttpGet(char *URL,char *localfile,char *proxyip,unsigned short proxyport)
197 {
198         m_ProxyEnabled = true;
199         m_ProxyIP = proxyip;
200         m_ProxyPort = proxyport;
201         GetFile(URL,localfile);
202 }
203
204 ChttpGet::ChttpGet(char *URL,char *localfile)
205 {
206         m_ProxyEnabled = false;
207         GetFile(URL,localfile);
208 }
209
210
211 void ChttpGet::GetFile(char *URL,char *localfile)
212 {
213         m_DataSock = INVALID_SOCKET;
214         m_iBytesIn = 0;
215         m_iBytesTotal = 0;
216         m_State = HTTP_STATE_STARTUP;;
217         m_Aborting = false;
218         m_Aborted = false;
219
220         strncpy(m_URL,URL,MAX_URL_LEN-1);
221         m_URL[MAX_URL_LEN-1] = 0;
222
223         LOCALFILE = fopen(localfile,"wb");
224         if(NULL == LOCALFILE)
225         {
226                 m_State = HTTP_STATE_CANT_WRITE_FILE;
227                 return;
228         }
229         m_DataSock = socket(AF_INET, SOCK_STREAM, 0);
230         if(INVALID_SOCKET == m_DataSock)
231         {
232                 m_State = HTTP_STATE_SOCKET_ERROR;
233                 return;
234         }
235         unsigned long arg;
236
237         arg = true;
238 //#ifndef __LINUX__
239 #ifndef PLAT_UNIX
240         ioctlsocket( m_DataSock, FIONBIO, &arg );
241 #else
242         ioctl( m_DataSock, FIONBIO, &arg );
243 #endif
244
245         char *pURL = URL;
246         if(strnicmp(URL,"http:",5)==0)
247         {
248                 pURL +=5;
249                 while(*pURL == '/')
250                 {
251                         pURL++;
252                 }
253         }
254         //There shouldn't be any : in this string
255         if(strchr(pURL,':'))
256         {
257                 m_State = HTTP_STATE_URL_PARSING_ERROR;
258                 return;
259         }
260         //read the filename by searching backwards for a /
261         //then keep reading until you find the first /
262         //when you found it, you have the host and dir
263         char *filestart = NULL;
264         char *dirstart = NULL;
265         for(int i = strlen(pURL);i>=0;i--)
266         {
267                 if(pURL[i]== '/')
268                 {
269                         if(!filestart)
270                         {
271                                 filestart = pURL+i+1;
272                                 dirstart = pURL+i+1;
273                                 strcpy(m_szFilename,filestart);
274                         }
275                         else
276                         {
277                                 dirstart = pURL+i+1;
278                         }
279                 }
280         }
281         if((dirstart==NULL) || (filestart==NULL))
282         {
283                 m_State = HTTP_STATE_URL_PARSING_ERROR;
284                 return;
285         }
286         else
287         {
288                 strcpy(m_szDir,dirstart);//,(filestart-dirstart));
289                 //m_szDir[(filestart-dirstart)] = NULL;
290                 strncpy(m_szHost,pURL,(dirstart-pURL));
291                 m_szHost[(dirstart-pURL)-1] = '\0';
292         }
293 // #ifdef WIN32
294         if(NULL==_beginthread(HTTPObjThread,0,this))
295         {
296                 m_State = HTTP_STATE_INTERNAL_ERROR;
297                 return;
298         }
299         /*
300 #elif defined(__LINUX__)
301         pthread_t thread;
302         if(!inet_LoadThreadLib())
303         {
304                 m_State = HTTP_STATE_INTERNAL_ERROR;
305                 return;
306         }
307         if(df_pthread_create(&thread,NULL,HTTPObjThread,this)!=0)
308         {
309                 m_State = HTTP_STATE_INTERNAL_ERROR;
310                 return;
311         }
312 #endif
313         */
314 }
315
316
317 ChttpGet::~ChttpGet()
318 {
319         if(m_DataSock != INVALID_SOCKET)
320         {
321                 shutdown(m_DataSock,2);
322 #ifndef __LINUX__
323                 closesocket(m_DataSock);
324 #else
325                 close(m_DataSock);
326 #endif
327         }
328 }
329
330 int ChttpGet::GetStatus()
331 {
332         return m_State;
333 }
334
335 unsigned int ChttpGet::GetBytesIn()
336 {
337         return m_iBytesIn;
338 }
339
340 unsigned int ChttpGet::GetTotalBytes()
341 {
342         return m_iBytesTotal;
343 }
344
345
346 void ChttpGet::WorkerThread()
347 {
348         char szCommand[1000];
349         char *p;
350         int irsp = 0;
351         ConnectSocket();
352         if(m_Aborting)
353         {
354                 fclose(LOCALFILE);
355                 return;
356         }
357         if(m_State != HTTP_STATE_CONNECTED)
358         {
359                 fclose(LOCALFILE);
360                 return;
361         }
362         sprintf(szCommand,"GET %s%s HTTP/1.1\nAccept: */*\nAccept-Encoding: deflate\nHost: %s\n\n\n",m_ProxyEnabled?"":"/",m_ProxyEnabled?m_URL:m_szDir,m_szHost);
363         send(m_DataSock,szCommand,strlen(szCommand),0);
364         p = GetHTTPLine();
365         if(strnicmp("HTTP/",p,5)==0)
366         {
367                 char *pcode;
368                 pcode = strchr(p,' ')+1;
369                 if(!pcode)
370                 {
371                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
372                         fclose(LOCALFILE);
373                         return;
374
375                 }
376                 pcode[3] = '\0';
377                 irsp = atoi(pcode);
378
379                 if(irsp == 0)
380                 {
381                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
382                         fclose(LOCALFILE);
383                         return;
384                 }
385                 if(irsp==200)
386                 {
387                         int idataready=0;
388                         do
389                         {
390                                 p = GetHTTPLine();
391                                 if(p==NULL)
392                                 {
393                                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
394                                         fclose(LOCALFILE);
395                                         return;
396                                 }
397                                 if(*p=='\0')
398                                 {
399                                         idataready = 1;
400                                         break;
401                                 }
402                                 if(strnicmp(p,"Content-Length:",strlen("Content-Length:"))==0)
403                                 {
404                                         char *s = strchr(p,' ')+1;
405                                         p = s;
406                                         if(s)
407                                         {
408                                                 while(*s)
409                                                 {
410                                                         if(!isdigit(*s))
411                                                         {
412                                                                 *s='\0';
413                                                         }
414                                                         s++;
415                                                 };
416                                                 m_iBytesTotal = atoi(p);
417                                         }
418
419                                 }
420
421                                 Sleep(1);
422                         }while(!idataready);
423                 ReadDataChannel();
424                 return;
425                 }
426                 m_State = HTTP_STATE_FILE_NOT_FOUND;
427                 fclose(LOCALFILE);
428                 return;
429         }
430         else
431         {
432                 m_State = HTTP_STATE_UNKNOWN_ERROR;
433                 fclose(LOCALFILE);
434                 return;
435         }
436 }
437
438 int ChttpGet::ConnectSocket()
439 {
440         //HOSTENT *he;
441         unsigned int ip;
442         SERVENT *se;
443         SOCKADDR_IN hostaddr;
444         if(m_Aborting){
445                 return 0;
446         }
447         
448         ip = inet_addr((const char *)m_szHost);
449
450         int rcode = 0;
451         if(ip==INADDR_NONE)
452         {
453                 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_szHost);          
454                 do
455                 {       
456                         if(m_Aborting)
457                         {
458                                 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_szHost);
459                                 return 0;
460                         }
461                         rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_szHost);
462
463                         Sleep(1);
464                 }while(rcode==0);
465         }
466         
467         if(rcode == -1)
468         {
469                 m_State = HTTP_STATE_HOST_NOT_FOUND;
470                 return 0;
471         }
472         //m_ControlSock
473         if(m_Aborting)
474                 return 0;
475         se = getservbyname("http", NULL);
476         if(m_Aborting)
477                 return 0;
478         if(se == NULL)
479         {
480                 hostaddr.sin_port = htons(80);
481         }
482         else
483         {
484                 hostaddr.sin_port = se->s_port;
485         }
486         hostaddr.sin_family = AF_INET;          
487         //ip = htonl(ip);
488         memcpy(&hostaddr.sin_addr,&ip,4);
489
490         if(m_ProxyEnabled)
491         {
492                 //This is on a proxy, so we need to make sure to connect to the proxy machine
493                 ip = inet_addr((const char *)m_ProxyIP);
494                                 
495                 if(ip==INADDR_NONE)
496                 {
497                         http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_ProxyIP);
498                         rcode = 0;
499                         do
500                         {       
501                                 if(m_Aborting)
502                                 {
503                                         http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_ProxyIP);
504                                         return 0;
505                                 }
506                                 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_ProxyIP);
507
508                                 Sleep(1);
509                         }while(rcode==0);
510                         
511                         
512                         if(rcode == -1)
513                         {
514                                 m_State = HTTP_STATE_HOST_NOT_FOUND;
515                                 return 0;
516                         }
517
518                 }
519                 //Use either the proxy port or 80 if none specified
520                 hostaddr.sin_port = htons((ushort)(m_ProxyPort ? m_ProxyPort : 80));
521                 //Copy the proxy address...
522                 memcpy(&hostaddr.sin_addr,&ip,4);
523
524         }
525         //Now we will connect to the host                                       
526         fd_set  wfds;
527
528         timeval timeout;
529         timeout.tv_sec = 0;
530         timeout.tv_usec = 0;
531         int serr = connect(m_DataSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR));
532         int cerr = WSAGetLastError();
533         if(serr)
534         {
535                 while((cerr==WSAEALREADY)||(cerr==WSAEINVAL)||(cerr==WSAEWOULDBLOCK))
536                 {
537                         FD_ZERO(&wfds);
538                         FD_SET( m_DataSock, &wfds );
539                         if(select(0,NULL,&wfds,NULL,&timeout))
540                         {
541                                 serr = 0;
542                                 break;
543                         }
544                         if(m_Aborting)
545                                 return 0;
546                         serr = connect(m_DataSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR));
547                         if(serr == 0)
548                                 break;
549                         cerr = WSAGetLastError();
550                         if(cerr==WSAEISCONN)
551                         {
552                                 serr = 0;
553                                 break;
554                         }
555
556                         Sleep(1);
557                 };
558         }
559         if(serr)
560         {
561                 m_State = HTTP_STATE_CANT_CONNECT;
562                 return 0;
563         }
564         m_State = HTTP_STATE_CONNECTED;
565         return 1;
566 }
567
568 char *ChttpGet::GetHTTPLine()
569 {
570         int iBytesRead;
571         char chunk[2];
572         unsigned int igotcrlf = 0;
573         memset(recv_buffer,0,1000);
574         do
575         {
576                 chunk[0]='\0';
577                 bool gotdata = false;
578                 do
579                 {
580                         iBytesRead = recv(m_DataSock,chunk,1,0);
581
582                         if(SOCKET_ERROR == iBytesRead)
583                         {       
584                                 int error = WSAGetLastError();
585                                 if(WSAEWOULDBLOCK==error)
586                                 {
587                                         gotdata = false;
588                                         continue;
589                                 }
590                                 else
591                                         return NULL;
592                         }
593                         else
594                         {
595                                 gotdata = true;
596                         }
597
598                         Sleep(1);
599                 }while(!gotdata);
600                 
601                 if(chunk[0]==0x0d)
602                 {
603                         //This should always read a 0x0a
604                         do
605                         {
606                                 iBytesRead = recv(m_DataSock,chunk,1,0);
607
608                                 if(SOCKET_ERROR == iBytesRead)
609                                 {       
610                                         int error = WSAGetLastError();
611                                         if(WSAEWOULDBLOCK==error)
612                                         {
613                                                 gotdata = false;
614                                                 continue;
615                                         }
616                                         else
617                                                 return NULL;
618                                 }
619                                 else
620                                 {
621                                         gotdata = true;
622                                 }
623
624                                 Sleep(1);
625                         }while(!gotdata);
626                         igotcrlf = 1;   
627                 }
628                 else
629                 {       chunk[1] = '\0';
630                         strcat(recv_buffer,chunk);
631                 }
632                 
633                 Sleep(1);
634         }while(igotcrlf==0);
635         return recv_buffer;     
636 }
637
638 unsigned int ChttpGet::ReadDataChannel()
639 {
640         char sDataBuffer[4096];         // Data-storage buffer for the data channel
641         int nBytesRecv = 0;                                             // Bytes received from the data channel
642
643         fd_set  wfds;
644
645         timeval timeout;
646         timeout.tv_sec = 0;
647         timeout.tv_usec = 500;
648
649         m_State = HTTP_STATE_RECEIVING;                 
650    do   
651    {
652                 FD_ZERO(&wfds);
653                 FD_SET( m_DataSock, &wfds );
654
655                 if((m_iBytesTotal)&&(m_iBytesIn==m_iBytesTotal))
656                 {
657                         break;
658                 }
659                 select(0,&wfds,NULL,NULL,&timeout);
660         if(m_Aborting)
661                 {
662                         fclose(LOCALFILE);
663                         return 0;               
664                 }
665                 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
666         if(m_Aborting)
667                 {
668                         fclose(LOCALFILE);
669                         return 0;
670                 }
671                 if(SOCKET_ERROR == nBytesRecv)
672                 {       
673                         int error = WSAGetLastError();
674                         if(WSAEWOULDBLOCK==error)
675                         {
676                                 nBytesRecv = 1;
677                                 continue;
678                         }
679                 }
680                 m_iBytesIn += nBytesRecv;
681                 if (nBytesRecv > 0 )
682                 {
683                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
684                         //Write sDataBuffer, nBytesRecv
685         }
686                 
687                 Sleep(1);
688         }while (nBytesRecv > 0);
689         fclose(LOCALFILE);                                                      
690         // Close the file and check for error returns.
691         if (nBytesRecv == SOCKET_ERROR)
692         { 
693                 //Ok, we got a socket error -- xfer aborted?
694                 m_State = HTTP_STATE_RECV_FAILED;
695                 return 0;
696         }
697         else
698         {
699                 //OutputDebugString("HTTP File complete!\n");
700                 //done!
701                 m_State = HTTP_STATE_FILE_RECEIVED;
702                 return 1;
703         }
704 }       
705
706
707 typedef struct _async_dns_lookup
708 {
709         unsigned int ip;        //resolved host. Write only to worker thread.
710         char * host;//host name to resolve. read only to worker thread
711         bool done;      //write only to the worker thread. Signals that the operation is complete
712         bool error; //write only to worker thread. Thread sets this if the name doesn't resolve
713         bool abort;     //read only to worker thread. If this is set, don't fill in the struct.
714 }async_dns_lookup;
715
716 async_dns_lookup httpaslu;
717 async_dns_lookup *http_lastaslu = NULL;
718
719 #ifndef __LINUX__
720 void __cdecl http_gethostbynameworker(void *parm);
721 #else
722 void *http_gethostbynameworker(void *parm);
723 #endif
724
725 int http_Asyncgethostbyname(unsigned int *ip,int command, char *hostname)
726 {
727         
728         if(command==NW_AGHBN_LOOKUP)
729         {
730                 if(http_lastaslu)
731                         http_lastaslu->abort = true;
732
733                 async_dns_lookup *newaslu;
734                 newaslu = (async_dns_lookup *)malloc(sizeof(async_dns_lookup));
735                 memset(&newaslu->ip,0,sizeof(unsigned int));
736                 newaslu->host = hostname;
737                 newaslu->done = false;
738                 newaslu->error = false;
739                 newaslu->abort = false;
740                 http_lastaslu = newaslu;
741                 httpaslu.done = false;
742
743 // #ifdef WIN32
744                 _beginthread(http_gethostbynameworker,0,newaslu);
745         /*
746 #elif defined(__LINUX__)
747                 pthread_t thread;
748                 if(!inet_LoadThreadLib())
749                 {
750                         return 0;
751                 }
752
753                 df_pthread_create(&thread,NULL,http_gethostbynameworker,newaslu);
754 #endif
755                 */
756                 return 1;
757         }
758         else if(command==NW_AGHBN_CANCEL)
759         {
760                 if(http_lastaslu)
761                         http_lastaslu->abort = true;
762                 http_lastaslu = NULL;
763         }
764         else if(command==NW_AGHBN_READ)
765         {
766                 if(!http_lastaslu)
767                         return -1;
768                 if(httpaslu.done)
769                 {
770                         //free(http_lastaslu);
771                         http_lastaslu = NULL;
772                         memcpy(ip,&httpaslu.ip,sizeof(unsigned int));
773                         return 1;
774                 }
775                 else if(httpaslu.error)
776                 {
777                         free(http_lastaslu);
778                         http_lastaslu = NULL;
779                         return -1;
780                 }
781                 else return 0;
782         }
783         return -2;
784
785 }
786
787 // This is the worker thread which does the lookup.
788 #ifndef __LINUX__
789 void __cdecl http_gethostbynameworker(void *parm)
790 #else
791 void *http_gethostbynameworker(void *parm)
792 #endif
793 {
794 #ifdef __LINUX__
795         df_pthread_detach(df_pthread_self());
796 #endif
797         async_dns_lookup *lookup = (async_dns_lookup *)parm;
798         HOSTENT *he = gethostbyname(lookup->host);
799         if(he==NULL)
800         {
801                 lookup->error = true;
802                 #ifdef __LINUX__
803                 return NULL;
804                 #else
805                 return;
806                 #endif
807         }
808         else if(!lookup->abort)
809         {
810                 memcpy(&lookup->ip,he->h_addr_list[0],sizeof(unsigned int));
811                 lookup->done = true;
812                 memcpy(&httpaslu,lookup,sizeof(async_dns_lookup));
813         }
814         free(lookup);
815
816 #ifdef __LINUX__
817         return NULL;
818 #endif
819 }
820