|
|
1.1 root 1: /* js_msg_area.c */
2:
3: /* Synchronet JavaScript "Message Area" Object */
4:
1.1.1.2 ! root 5: /* $Id: js_msg_area.c,v 1.57 2009/11/11 01:49:18 deuce Exp $ */
1.1 root 6:
7: /****************************************************************************
8: * @format.tab-size 4 (Plain Text/Source Code File Header) *
9: * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
10: * *
1.1.1.2 ! root 11: * Copyright 2009 Rob Swindell - http://www.synchro.net/copyright.html *
1.1 root 12: * *
13: * This program is free software; you can redistribute it and/or *
14: * modify it under the terms of the GNU General Public License *
15: * as published by the Free Software Foundation; either version 2 *
16: * of the License, or (at your option) any later version. *
17: * See the GNU General Public License for more details: gpl.txt or *
18: * http://www.fsf.org/copyleft/gpl.html *
19: * *
20: * Anonymous FTP access to the most recent released source is available at *
21: * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
22: * *
23: * Anonymous CVS access to the development source and modification history *
24: * is available at cvs.synchro.net:/cvsroot/sbbs, example: *
25: * cvs -d :pserver:[email protected]:/cvsroot/sbbs login *
26: * (just hit return, no password is necessary) *
27: * cvs -d :pserver:[email protected]:/cvsroot/sbbs checkout src *
28: * *
29: * For Synchronet coding style and modification guidelines, see *
30: * http://www.synchro.net/source.html *
31: * *
32: * You are encouraged to submit any modifications (preferably in Unix diff *
33: * format) via e-mail to [email protected] *
34: * *
35: * Note: If this box doesn't appear square, then you need to fix your tabs. *
36: ****************************************************************************/
37:
38: #include "sbbs.h"
39:
40: #ifdef JAVASCRIPT
41:
42: enum { /* msg_area Object Properties */
43: PROP_MAX_QWK_MSGS
44: };
45:
46: #ifdef BUILD_JSDOCS
47:
48: static char* msg_grp_prop_desc[] = {
49: "index into grp_list array (or -1 if not in array) <i>(introduced in v3.12)</i>"
50: ,"unique number for this message group"
51: ,"group name"
52: ,"group description"
53: ,"group access requirements"
54: ,NULL
55: };
56:
57: static char* msg_area_prop_desc[] = {
58:
59: "index into sub_list array (or -1 if not in array) <i>(introduced in v3.12)</i>"
60: ,"group's index into grp_list array <i>(introduced in v3.12)</i>"
61: ,"unique number for this sub-board"
62: ,"group number"
63: ,"group name <i>(introduced in v3.12)</i>"
64: ,"sub-board internal code"
65: ,"sub-board name"
66: ,"sub-board description"
67: ,"sub-board QWK name"
68: ,"newsgroup name (as configured or dymamically generated)"
69: ,"sub-board access requirements"
70: ,"sub-board reading requirements"
71: ,"sub-board posting requirements"
72: ,"sub-board operator requirements"
73: ,"sub-board moderated-user requirements (if non-blank)"
74: ,"sub-board data storage location"
75: ,"FidoNet origin line"
76: ,"QWK Network tagline"
77: ,"toggle options (bitfield)"
78: ,"index into message scan configuration/pointer file"
79: ,"QWK conference number"
80: ,"configured maximum number of message CRCs to store (for dupe checking)"
81: ,"configured maximum number of messages before purging"
82: ,"configured maximum age (in days) of messages before expiration"
83: /* Insert here */
84: ,"user has sufficient access to read messages"
85: ,"user has sufficient access to post messages"
86: ,"user has operator access to this message area"
87: ,"user's posts are moderated"
88: ,"user's current new message scan pointer (highest-read message number)"
89: ,"user's message scan configuration (bitfield) see <tt>SCAN_CFG_*</tt> in <tt>sbbsdefs.js</tt> for valid bits"
90: ,"user's last-read message number"
91: ,NULL
92: };
93: #endif
94:
95: BOOL DLLCALL js_CreateMsgAreaProperties(JSContext* cx, scfg_t* cfg, JSObject* subobj, uint subnum)
96: {
97: char str[128];
98: int c;
99: JSString* js_str;
100: jsval val;
101: sub_t* sub;
102:
103: if(subnum==INVALID_SUB || subnum>=cfg->total_subs)
104: return(FALSE);
105:
106: sub=cfg->sub[subnum];
107:
108: if(!JS_DefineProperty(cx, subobj, "number", INT_TO_JSVAL(subnum)
109: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
110: return(FALSE);
111:
112: if(!JS_DefineProperty(cx, subobj, "grp_number", INT_TO_JSVAL(sub->grp)
113: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
114: return(FALSE);
115:
116: if((js_str=JS_NewStringCopyZ(cx, cfg->grp[sub->grp]->sname))==NULL)
117: return(FALSE);
118: if(!JS_DefineProperty(cx, subobj, "grp_name", STRING_TO_JSVAL(js_str)
119: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
120: return(FALSE);
121:
122: if((js_str=JS_NewStringCopyZ(cx, sub->code))==NULL)
123: return(FALSE);
124: if(!JS_DefineProperty(cx, subobj, "code", STRING_TO_JSVAL(js_str)
125: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
126: return(FALSE);
127:
128: if((js_str=JS_NewStringCopyZ(cx, sub->sname))==NULL)
129: return(FALSE);
130: if(!JS_DefineProperty(cx, subobj, "name", STRING_TO_JSVAL(js_str)
131: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
132: return(FALSE);
133:
134: if((js_str=JS_NewStringCopyZ(cx, sub->lname))==NULL)
135: return(FALSE);
136: if(!JS_DefineProperty(cx, subobj, "description", STRING_TO_JSVAL(js_str)
137: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
138: return(FALSE);
139:
140: if((js_str=JS_NewStringCopyZ(cx, sub->qwkname))==NULL)
141: return(FALSE);
142: if(!JS_DefineProperty(cx, subobj, "qwk_name", STRING_TO_JSVAL(js_str)
143: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
144: return(FALSE);
145:
146: if(sub->newsgroup[0])
147: SAFECOPY(str,sub->newsgroup);
148: else {
149: sprintf(str,"%s.%s",cfg->grp[sub->grp]->sname,sub->sname);
150: for(c=0;str[c];c++)
151: if(str[c]==' ')
152: str[c]='_';
153: }
154: if((js_str=JS_NewStringCopyZ(cx, str))==NULL)
155: return(FALSE);
156: if(!JS_DefineProperty(cx, subobj, "newsgroup", STRING_TO_JSVAL(js_str)
157: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
158: return(FALSE);
159:
160: if((js_str=JS_NewStringCopyZ(cx, sub->arstr))==NULL)
161: return(FALSE);
162: if(!JS_DefineProperty(cx, subobj, "ars", STRING_TO_JSVAL(js_str)
163: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
164: return(FALSE);
165:
166: if((js_str=JS_NewStringCopyZ(cx, sub->read_arstr))==NULL)
167: return(FALSE);
168: if(!JS_DefineProperty(cx, subobj, "read_ars", STRING_TO_JSVAL(js_str)
169: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
170: return(FALSE);
171:
172: if((js_str=JS_NewStringCopyZ(cx, sub->post_arstr))==NULL)
173: return(FALSE);
174: if(!JS_DefineProperty(cx, subobj, "post_ars", STRING_TO_JSVAL(js_str)
175: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
176: return(FALSE);
177:
178: if((js_str=JS_NewStringCopyZ(cx, sub->op_arstr))==NULL)
179: return(FALSE);
180: if(!JS_DefineProperty(cx, subobj, "operator_ars", STRING_TO_JSVAL(js_str)
181: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
182: return(FALSE);
183:
184: if((js_str=JS_NewStringCopyZ(cx, sub->mod_arstr))==NULL)
185: return(FALSE);
186: if(!JS_DefineProperty(cx, subobj, "moderated_ars", STRING_TO_JSVAL(js_str)
187: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
188: return(FALSE);
189:
190: if((js_str=JS_NewStringCopyZ(cx, sub->data_dir))==NULL)
191: return(FALSE);
192: if(!JS_DefineProperty(cx, subobj, "data_dir", STRING_TO_JSVAL(js_str)
193: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
194: return(FALSE);
195:
196: if((js_str=JS_NewStringCopyZ(cx, sub->origline))==NULL)
197: return(FALSE);
198: if(!JS_DefineProperty(cx, subobj, "fidonet_origin", STRING_TO_JSVAL(js_str)
199: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
200: return(FALSE);
201:
202: if((js_str=JS_NewStringCopyZ(cx, sub->tagline))==NULL)
203: return(FALSE);
204: if(!JS_DefineProperty(cx, subobj, "qwknet_tagline", STRING_TO_JSVAL(js_str)
205: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
206: return(FALSE);
207:
208: if(!JS_NewNumberValue(cx,sub->misc,&val))
209: return(FALSE);
210: if(!JS_DefineProperty(cx, subobj, "settings", val
211: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
212: return(FALSE);
213:
214: if(!JS_DefineProperty(cx, subobj, "ptridx", INT_TO_JSVAL(sub->ptridx)
215: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
216: return(FALSE);
217:
218: if(!JS_DefineProperty(cx, subobj, "qwk_conf", INT_TO_JSVAL(sub->qwkconf)
219: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
220: return(FALSE);
221:
222: if(!JS_DefineProperty(cx, subobj, "max_crcs", INT_TO_JSVAL(sub->maxcrcs)
223: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
224: return(FALSE);
225:
226: if(!JS_DefineProperty(cx, subobj, "max_msgs", INT_TO_JSVAL(sub->maxmsgs)
227: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
228: return(FALSE);
229:
230: if(!JS_DefineProperty(cx, subobj, "max_age", INT_TO_JSVAL(sub->maxage)
231: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
232: return(FALSE);
233:
234: #ifdef BUILD_JSDOCS
235: js_CreateArrayOfStrings(cx, subobj, "_property_desc_list", msg_area_prop_desc, JSPROP_READONLY);
236: #endif
237:
238: return(TRUE);
239: }
240:
241: /*******************************************/
242: /* Re-writable Sub-board Object Properites */
243: /*******************************************/
244: enum {
245: SUB_PROP_SCAN_PTR
246: ,SUB_PROP_SCAN_CFG
247: ,SUB_PROP_LAST_READ
248: };
249:
250:
251: static JSBool js_sub_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
252: {
253: jsint tiny;
254: subscan_t* scan;
255:
256: if((scan=(subscan_t*)JS_GetPrivate(cx,obj))==NULL)
257: return(JS_TRUE);
258:
259: tiny = JSVAL_TO_INT(id);
260:
261: switch(tiny) {
262: case SUB_PROP_SCAN_PTR:
263: JS_NewNumberValue(cx,scan->ptr,vp);
264: break;
265: case SUB_PROP_SCAN_CFG:
266: JS_NewNumberValue(cx,scan->cfg,vp);
267: break;
268: case SUB_PROP_LAST_READ:
269: JS_NewNumberValue(cx,scan->last,vp);
270: break;
271: }
272:
273: return(JS_TRUE);
274: }
275:
276: static JSBool js_sub_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
277: {
278: int32 val=0;
279: jsint tiny;
280: subscan_t* scan;
281:
282: if((scan=(subscan_t*)JS_GetPrivate(cx,obj))==NULL)
283: return(JS_TRUE);
284:
285: tiny = JSVAL_TO_INT(id);
286:
287: switch(tiny) {
288: case SUB_PROP_SCAN_PTR:
289: JS_ValueToInt32(cx, *vp, (int32*)&scan->ptr);
290: break;
291: case SUB_PROP_SCAN_CFG:
292: JS_ValueToInt32(cx, *vp, &val);
293: scan->cfg=(ushort)val;
294: break;
295: case SUB_PROP_LAST_READ:
296: JS_ValueToInt32(cx, *vp, (int32*)&scan->last);
297: break;
298: }
299:
300: return(JS_TRUE);
301: }
302:
303: static struct JSPropertySpec js_sub_properties[] = {
304: /* name ,tinyid ,flags */
305:
306: { "scan_ptr" ,SUB_PROP_SCAN_PTR ,JSPROP_ENUMERATE|JSPROP_SHARED },
307: { "scan_cfg" ,SUB_PROP_SCAN_CFG ,JSPROP_ENUMERATE|JSPROP_SHARED },
1.1.1.2 ! root 308: { "last_read" ,SUB_PROP_LAST_READ ,JSPROP_ENUMERATE|JSPROP_SHARED },
1.1 root 309: {0}
310: };
311:
312:
313: static JSClass js_sub_class = {
314: "MsgSub" /* name */
315: ,JSCLASS_HAS_PRIVATE /* flags */
316: ,JS_PropertyStub /* addProperty */
317: ,JS_PropertyStub /* delProperty */
318: ,js_sub_get /* getProperty */
319: ,js_sub_set /* setProperty */
320: ,JS_EnumerateStub /* enumerate */
321: ,JS_ResolveStub /* resolve */
322: ,JS_ConvertStub /* convert */
323: ,JS_FinalizeStub /* finalize */
324: };
325:
326: JSObject* DLLCALL js_CreateMsgAreaObject(JSContext* cx, JSObject* parent, scfg_t* cfg
1.1.1.2 ! root 327: ,user_t* user, client_t* client, subscan_t* subscan)
1.1 root 328: {
329: JSObject* areaobj;
330: JSObject* allgrps;
331: JSObject* allsubs;
1.1.1.2 ! root 332: JSObject* grpobj_proto;
1.1 root 333: JSObject* grpobj;
1.1.1.2 ! root 334: JSObject* subobj_proto;
1.1 root 335: JSObject* subobj;
336: JSObject* grp_list;
337: JSObject* sub_list;
338: JSString* js_str;
339: jsval val;
340: jsuint grp_index;
341: jsuint sub_index;
342: uint l,d;
343:
344: /* Return existing object if it's already been created */
345: if(JS_GetProperty(cx,parent,"msg_area",&val) && val!=JSVAL_VOID)
346: areaobj = JSVAL_TO_OBJECT(val);
347: else
348: areaobj = JS_DefineObject(cx, parent, "msg_area", NULL
349: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
350:
351: if(areaobj==NULL)
352: return(NULL);
353:
354: #ifdef BUILD_JSDOCS
355: js_DescribeSyncObject(cx,areaobj,"Message Areas",310);
356: #endif
357:
358: /* msg_area.grp[] */
359: if((allgrps=JS_NewObject(cx, NULL, NULL, areaobj))==NULL)
360: return(NULL);
361:
362: val=OBJECT_TO_JSVAL(allgrps);
363: if(!JS_SetProperty(cx, areaobj, "grp", &val))
364: return(NULL);
365:
366: /* msg_area.sub[] */
367: if((allsubs=JS_NewObject(cx, NULL, NULL, areaobj))==NULL)
368: return(NULL);
369:
370: val=OBJECT_TO_JSVAL(allsubs);
371: if(!JS_SetProperty(cx, areaobj, "sub", &val))
372: return(NULL);
373:
374: /* msg_area.grp_list[] */
375: if((grp_list=JS_NewArrayObject(cx, 0, NULL))==NULL)
376: return(NULL);
377:
378: val=OBJECT_TO_JSVAL(grp_list);
379: if(!JS_SetProperty(cx, areaobj, "grp_list", &val))
380: return(NULL);
381:
1.1.1.2 ! root 382: if((grpobj_proto=JS_NewObject(cx, NULL, NULL, areaobj))==NULL)
! 383: return(NULL);
! 384: if((subobj_proto=JS_NewObject(cx, NULL, NULL, areaobj))==NULL)
! 385: return(NULL);
1.1 root 386: for(l=0;l<cfg->total_grps;l++) {
387:
1.1.1.2 ! root 388: if((grpobj=JS_NewObject(cx, NULL, grpobj_proto, NULL))==NULL)
1.1 root 389: return(NULL);
390:
391: val=OBJECT_TO_JSVAL(grpobj);
392: grp_index=-1;
1.1.1.2 ! root 393: if(user==NULL || chk_ar(cfg,cfg->grp[l]->ar,user,client)) {
1.1 root 394:
395: if(!JS_GetArrayLength(cx, grp_list, &grp_index))
396: return(NULL);
397:
398: if(!JS_SetElement(cx, grp_list, grp_index, &val))
399: return(NULL);
400: }
401:
402: /* Add as property (associative array element) */
403: if(!JS_DefineProperty(cx, allgrps, cfg->grp[l]->sname, val
404: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE))
405: return(NULL);
406:
407: val=INT_TO_JSVAL(grp_index);
408: if(!JS_SetProperty(cx, grpobj, "index", &val))
409: return(NULL);
410:
411: val=INT_TO_JSVAL(l);
412: if(!JS_SetProperty(cx, grpobj, "number", &val))
413: return(NULL);
414:
415: if((js_str=JS_NewStringCopyZ(cx, cfg->grp[l]->sname))==NULL)
416: return(NULL);
417: val=STRING_TO_JSVAL(js_str);
418: if(!JS_SetProperty(cx, grpobj, "name", &val))
419: return(NULL);
420:
421: if((js_str=JS_NewStringCopyZ(cx, cfg->grp[l]->lname))==NULL)
422: return(NULL);
423: val=STRING_TO_JSVAL(js_str);
424: if(!JS_SetProperty(cx, grpobj, "description", &val))
425: return(NULL);
426:
427: if((js_str=JS_NewStringCopyZ(cx, cfg->grp[l]->arstr))==NULL)
428: return(NULL);
429: if(!JS_DefineProperty(cx, grpobj, "ars", STRING_TO_JSVAL(js_str)
430: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY))
431: return(NULL);
432:
433: #ifdef BUILD_JSDOCS
434: js_DescribeSyncObject(cx,grpobj,"Message Groups (current user has access to)",310);
435: #endif
436:
437: /* sub_list[] */
438: if((sub_list=JS_NewArrayObject(cx, 0, NULL))==NULL)
439: return(NULL);
440:
441: val=OBJECT_TO_JSVAL(sub_list);
442: if(!JS_SetProperty(cx, grpobj, "sub_list", &val))
443: return(NULL);
444:
445: for(d=0;d<cfg->total_subs;d++) {
446: if(cfg->sub[d]->grp!=l)
447: continue;
448:
1.1.1.2 ! root 449: if((subobj=JS_NewObject(cx, &js_sub_class, subobj_proto, NULL))==NULL)
1.1 root 450: return(NULL);
451:
452: if(subscan!=NULL)
453: JS_SetPrivate(cx,subobj,&subscan[d]);
454:
455: val=OBJECT_TO_JSVAL(subobj);
456: sub_index=-1;
1.1.1.2 ! root 457: if(user==NULL || can_user_access_sub(cfg,d,user,client)) {
1.1 root 458:
459: if(!JS_GetArrayLength(cx, sub_list, &sub_index))
460: return(NULL);
461:
462: if(!JS_SetElement(cx, sub_list, sub_index, &val))
463: return(NULL);
464: }
465:
466: /* Add as property (associative array element) */
467: if(!JS_DefineProperty(cx, allsubs, cfg->sub[d]->code, val
468: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE))
469: return(NULL);
470:
471: val=INT_TO_JSVAL(sub_index);
472: if(!JS_SetProperty(cx, subobj, "index", &val))
473: return(NULL);
474:
475: val=INT_TO_JSVAL(grp_index);
476: if(!JS_SetProperty(cx, subobj, "grp_index", &val))
477: return(NULL);
478:
479: if(!js_CreateMsgAreaProperties(cx, cfg, subobj, d))
480: return(NULL);
1.1.1.2 ! root 481:
! 482: if(user==NULL)
1.1 root 483: val=BOOLEAN_TO_JSVAL(JS_TRUE);
484: else
1.1.1.2 ! root 485: val=BOOLEAN_TO_JSVAL(can_user_read_sub(cfg,d,user,client));
1.1 root 486: if(!JS_SetProperty(cx, subobj, "can_read", &val))
487: return(NULL);
488:
489: if(user==NULL)
490: val=BOOLEAN_TO_JSVAL(JS_TRUE);
491: else
1.1.1.2 ! root 492: val=BOOLEAN_TO_JSVAL(can_user_post(cfg,d,user,client,/* reason: */NULL));
1.1 root 493: if(!JS_SetProperty(cx, subobj, "can_post", &val))
494: return(NULL);
495:
1.1.1.2 ! root 496: if(user==NULL)
1.1 root 497: val=BOOLEAN_TO_JSVAL(JS_TRUE);
498: else
1.1.1.2 ! root 499: val=BOOLEAN_TO_JSVAL(is_user_subop(cfg,d,user,client));
1.1 root 500: if(!JS_SetProperty(cx, subobj, "is_operator", &val))
501: return(NULL);
502:
503: if(cfg->sub[d]->mod_ar[0]!=0 && user!=NULL
1.1.1.2 ! root 504: && chk_ar(cfg,cfg->sub[d]->mod_ar,user,client))
1.1 root 505: val=BOOLEAN_TO_JSVAL(JS_TRUE);
506: else
507: val=BOOLEAN_TO_JSVAL(JS_FALSE);
508: if(!JS_SetProperty(cx, subobj, "is_moderated", &val))
509: return(NULL);
510:
511: if(!JS_DefineProperties(cx, subobj, js_sub_properties))
512: return(NULL);
513:
514: #ifdef BUILD_JSDOCS
515: js_DescribeSyncObject(cx,subobj,"Message Sub-boards (current user has access to)</h2>"
516: "(all properties are <small>READ ONLY</small> except for "
517: "<i>scan_ptr</i>, <i>scan_cfg</i>, and <i>last_read</i>)"
518: ,310);
519: #endif
520:
521: }
522:
523: #ifdef BUILD_JSDOCS
524: js_CreateArrayOfStrings(cx, grpobj, "_property_desc_list", msg_grp_prop_desc, JSPROP_READONLY);
525: #endif
526: }
527:
528: #ifdef BUILD_JSDOCS
529: js_DescribeSyncObject(cx,allgrps,"Associative array of all groups (use name as index)",312);
530: JS_DefineProperty(cx,allgrps,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);
531: #endif
532:
533: #ifdef BUILD_JSDOCS
534: js_DescribeSyncObject(cx,allsubs,"Associative array of all sub-boards (use internal code as index)",311);
535: JS_DefineProperty(cx,allsubs,"_dont_document",JSVAL_TRUE,NULL,NULL,JSPROP_READONLY);
536: #endif
537:
538: return(areaobj);
539: }
540:
541: #endif /* JAVSCRIPT */
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.