Annotation of OpenXM/src/kan96xx/plugin/oxcgi.c, Revision 1.9
1.9 ! takayama 1: /* $OpenXM: OpenXM/src/kan96xx/plugin/oxcgi.c,v 1.8 2005/02/27 05:28:06 takayama Exp $ */
1.2 takayama 2: #include <stdio.h>
3: #include "datatype.h"
4: #include "stackm.h"
5: #include "extern.h"
6: #include "file2.h"
7: #include "oxcgi.h"
8:
9: static int ppp(char *s,int kstart,int kend, int vstart, int vend);
10: static int cgiHex(int p);
11:
12: static test1();
13: static test2();
14: static test3();
15: static test4();
16:
17: /* main() {KSstart();test4();} */
18:
19: /*
20: [["URL","http://www.openxm.org"],
21: ["foo","value1"], <--- the first key value.
22: ["hoge","value2"]
23: ]
24: */
25: struct object cgiUrlEncodingToKeyValuePair(char *s) {
26: int i,n,start;
27: int kstart,kend; /* start of key, end of key */
28: int vstart,vend; /* start of value, end of value */
29: int state;
30: int nOfPairs;
1.9 ! takayama 31: struct object rob = OINIT;
! 32: struct object ob = OINIT;
1.2 takayama 33: int k;
34: n = strlen(s); start = -1;
35: for (i=0; i<n; i++) {
36: if (s[i] == '?') { start=i+1; break;}
37: }
1.5 takayama 38: if (start == -1) {
39: start = 0;
40: for (i=0; i<n; i++) {
41: if (s[i] > ' ') { start = i; break; }
42: }
43: }
1.2 takayama 44: for (k=0; k<2; k++) {
45: /* k==0 path one. Count nOfPairs. */
46: /* k==1 path two. generate array. */
47: nOfPairs = 0;
48: i = start;
49: /* state 0, 1 : key
50: =
51: state 2, 3 : value
52: &
53: state 5 : after getting &
54: state 4 : after getting eof
55: state 6
56: */
57: state = 0;
58: while (1) {
59: switch(state) {
60: case 0:
61: kstart = kend = vstart = vend = -1;
62: if (s[i] <= ' ') {
63: state=6; break;
64: } else {
65: kstart = kend = i;
66: nOfPairs++;
67: i++;
68: state=1;
69: break;
70: }
71: case 1:
72: if (s[i] <= ' ') {
73: state=4; break;
74: }else if (s[i] == '=') {
75: state=2; i++; break;
76: }else {
77: kend = i; i++; break;
78: }
79: case 2:
80: vstart = vend = -1;
81: if (s[i] <= ' ') {
82: state=4; break;
83: }else if (s[i] == '&') {
84: state=5; break;
85: }else {
86: state=3; vstart=vend=i; i++; break;
87: }
88: case 3:
89: if (s[i] <= ' ') {
90: state=4; break;
91: }else if (s[i] == '&') {
92: state=5; break;
93: }else{
94: vend=i; i++; break;
95: }
96: case 4:
97: if (k == 1) {
98: ob = newObjectArray(2);
99: putoa(ob,0,urlEncodedStringToObj(s,kstart,kend,0));
100: putoa(ob,1,urlEncodedStringToObj(s,vstart,vend,0));
101: putoa(rob,nOfPairs,ob);
102: }
103: state = -1; break;
104: case 5:
105: if (k == 1) {
106: ob = newObjectArray(2);
107: putoa(ob,0,urlEncodedStringToObj(s,kstart,kend,0));
108: putoa(ob,1,urlEncodedStringToObj(s,vstart,vend,0));
109: putoa(rob,nOfPairs,ob);
110: }
111: i++; state = 0; break;
112: case 6: state = -1; break;
113: default: break;
114: }
115: if (state < 0) break;
116: /*
117: if ((state == 4) || (state == 5)) {
118: ppp(s,kstart,kend,vstart,vend);
119: }
120: */
121: }
122: if (k == 0) {
123: char *stmp; int ii;
124: rob = newObjectArray(nOfPairs+1);
125: ob = newObjectArray(2);
126: putoa(ob,0,KpoString("URL"));
127: stmp = sGC_malloc(start+2);
128: if (stmp == NULL) errorKan1("%s\n","No more memory.");
129: stmp[0] = 0;
130: for (ii=0; ii<start-1; ii++) {
131: stmp[ii] = s[ii]; stmp[ii+1] = 0;
132: }
133: putoa(ob,1,KpoString(stmp));
134: putoa(rob,0,ob);
135: }
136: }
137: return rob;
138: }
139:
140: /* . - _ A-Z a-z 0-9
141: space --> +
142: */
143: static int isUrlEncoding3(char s) {
144: if ((s == '.') || (s == '-') || (s == '_')) return(0);
145: if ((s >= 'A') && (s <= 'Z')) return(0);
146: if ((s >= 'a') && (s <= 'z')) return(0);
147: if ((s >= '0') && (s <= '9')) return(0);
148: if (s == ' ') return(0);
149: return(1);
150: }
151:
152: char *byteArrayToUrlEncoding(unsigned char *s,int size) {
153: int n,i,j;
154: char *r;
155: n = 0;
156: /* get Size */
157: for (i=0; i<size; i++) {
158: if (isUrlEncoding3((char)s[i])) n += 3;
159: n++;
160: }
161: r = sGC_malloc(n+1);
162: if (r == NULL) errorKan1("%s\n","No more memory.");
163: r[0] = 0; r[n] = 0;
164: i = 0; j = 0;
165: while ((j < n) && (i<size)) {
166: if (isUrlEncoding3((char)s[i])) {
167: sprintf(&(r[j]),"%%%02X",s[i]); j += 3;
168: }else{
169: if ((char)s[i] == ' ') r[j]='+';
170: else r[j] = s[i];
171: j++; r[j] = 0;
172: }
173: i++;
174: }
175: return(r);
176: }
177: struct object urlEncodedStringToObj(char *s,int vstart,int vend,int mode)
178: /*
179: mode == 0. Authmatically choose Sdollar or SbyteArray.
180: [ not implemented yet. ]
181: */
182: {
1.9 ! takayama 183: struct object rob = OINIT;
1.2 takayama 184: char *ts;
185: char *ts2;
186: int i,j;
187: int p;
188: if ((s == NULL) || (vstart < 0)) return(NullObject);
189: if (vend+1-vstart <= 0) return(NullObject);
190: ts = (char *) malloc(vend-vstart+1);
191: if (ts == NULL) errorKan1("%s\n","Out of memory.");
192: j = 0; ts[j] = 0;
193: for (i=vstart; i<=vend; i++,j++) {
194: ts[j] = 0;
195: if (s[i] == '+') {
196: ts[j] = ' '; ts[j+1] = 0;
197: }else if (s[i] == '%') {
198: p = cgiHex(s[i+1])*16+cgiHex(s[i+2]);
199: i = i+2;
200: ts[j] = p; ts[j+1] = 0;
201: }else {
202: ts[j] = s[i]; ts[j+1] = 0;
203: }
204: }
205: ts2 = (char *)sGC_malloc(j);
206: if (ts2 == NULL) errorKan1("%s\n","Out of memory.");
207: for (i=0; i<j; i++) {
208: ts2[i] = ts[i];
209: }
210: return KpoString(ts2);
211: }
212:
213: static int cgiHex(int p) {
214: if (p >= '0' && p <= '9') return (p-'0');
215: if (p >= 'A' && p <= 'F') return (p-'A'+10);
216: if (p >= 'a' && p <= 'f') return (p-'a'+10);
217: errorKan1("%s\n","Invalid argument to cgiHex.");
218: }
219:
220: /* for debug */
221: static int ppp(char *s,int kstart,int kend, int vstart, int vend) {
222: int i;
223: printf("%d %d %d %d\n",kstart,kend,vstart,vend);
224: if (kstart >= 0) {
225: printf("key=");
226: for (i=kstart; i<=kend; i++) putchar(s[i]);
227: printf("\n");
228: }
229: if (vstart >= 0) {
230: printf("value=");
231: for (i=vstart; i<=vend; i++) putchar(s[i]);
232: printf("\n");
233: }
234: }
235: static test1() {
236: char s[1000];
237: cgiUrlEncodingToKeyValuePair("http://hoge.hoge?name=1231232&hoge=asdfsdf&foo=asdfasdf");
238: cgiUrlEncodingToKeyValuePair("http://hoge.hoge?name=1231232&hoge=&foo=asdfasdf&");
239: scanf("%s",s);
240: cgiUrlEncodingToKeyValuePair(s);
241: }
242: static test2() {
243: char s[1000];
1.9 ! takayama 244: struct object ob = OINIT;
1.2 takayama 245: ob=cgiUrlEncodingToKeyValuePair("http://hoge.hoge?name=1231232&hoge=asdfsdf&foo=asdfasdf");
246: printObject(ob,1,stdout);
247: ob=cgiUrlEncodingToKeyValuePair("http://hoge.hoge?name=1231232&hoge=&foo=asdfasdf&hoge=A%41+%42%62y%21");
248: printObject(ob,1,stdout);
249: scanf("%s",s);
250: ob=cgiUrlEncodingToKeyValuePair(s);
251: printObject(ob,1,stdout);
252: }
253:
254: static test4() {
255: char s[1000];
1.9 ! takayama 256: struct object ob = OINIT;
1.2 takayama 257: char *ts;
258: int size;
259: ob=cgiUrlEncodingToKeyValuePair("http://hoge.hoge?name=1231232&hoge=&foo=asdfasdf&hoge=A%41+%42%62y%21");
260: printObject(ob,1,stdout);
261: ts = cgiKeyValuePairToUrlEncoding(ob);
262: printf("result=%s",ts);
263:
264:
265: ts = "Pragma: no-cache\nContent-Length: 2915\nContent-Type: text/html\nConnection: close\n\n <DIV class=Section1> \n <P class=MsoNormal \n style=\"mso-list: none; mso-list-ins: \" 19991102T2025\"> \n </P> ";
266:
267: ob=cgiHttpToKeyValuePair(ts,strlen(ts));
268: printObject(ob,1,stdout);
269: ts = cgiKeyValuePairToHttp(ob,&size);
270: printf("result:\n%s",ts);
271:
272: }
273: /* end for debug */
274:
275:
276: char *cgiKeyValuePairToUrlEncoding(struct object ob) {
277: FILE2 *fp;
278: int size;
279: fp = fp2open(-1);
280: if (fp == NULL) errorKan1("%s\n","cgiKeyValuePairToUrlEncoding: open error.");
281: cgiKeyValuePairToUrlEncodingFile2(ob,fp);
282: return fp2fcloseInString(fp,&size);
283: }
284: int checkKeyValuePairFormat(struct object ob,char *msg) {
285: int i,n;
1.9 ! takayama 286: struct object eob = OINIT;
! 287: struct object eob0 = OINIT;
! 288: struct object eob1 = OINIT;
1.2 takayama 289: static char *fmt = NULL;
290: int size;
291: char *ss;
292: size = 1024;
293: if (fmt == NULL) fmt = sGC_malloc(size);
294: if (fmt == NULL) errorKan1("%s\n","No more memory.");
295: for (i=0; i<size; i++) fmt[i]=0;
296: ss = "%s\n In ";
297: strcpy(fmt,ss);
298: strncpy(&(fmt[strlen(ss)]),msg,size-strlen(ss)-2);
299:
300: if (ob.tag != Sarray) errorKan1(fmt,"checkKeyValuePairFormat: argument must be an array.");
301: n = getoaSize(ob);
302: for (i=0; i<n ; i++) {
303: eob = getoa(ob,i);
304: if (eob.tag != Sarray) errorKan1(fmt,"checkKeyValuePairFormat: argument must be an array of arrays.");
305: if (getoaSize(eob) != 2) errorKan1(fmt,"checkKeyValuePairFormat: argument must be an array of arrays of size 2.");
306: eob0 = getoa(eob,0); eob1 = getoa(eob,1);
307: if (eob0.tag != Sdollar) errorKan1(fmt,"checkKeyValuePairFormat: the key word must be a string.\n");
308: }
309: return 0;
310: }
311:
312: int cgiKeyValuePairToUrlEncodingFile2(struct object ob,FILE2 *fp) {
313: int n,i;
1.9 ! takayama 314: struct object eob = OINIT;
! 315: struct object eob0 = OINIT;
! 316: struct object eob1 = OINIT;
1.2 takayama 317: char *key, *s;
318: checkKeyValuePairFormat(ob,"cgiKeyValuePairToUrlEncodingFile2");
319: n = getoaSize(ob);
320: for (i=0; i<n; i++) {
321: eob = getoa(ob,i);
322: eob0 = getoa(eob,0); eob1 = getoa(eob,1);
323: key = KopString(eob0);
1.7 takayama 324: if ((i == 0) && (strcmp(key,"URL")==0)) {
1.2 takayama 325: if (eob1.tag != Sdollar) errorKan1("%s\n","URL value must be a string.");
326: fp2fputs(KopString(eob1),fp);
327: if ( n > 1 ) fp2fputc('?',fp);
328: }else{
329: fp2fputs(key,fp); fp2fputc('=',fp);
330: if (eob1.tag == Snull) ;
331: else if (eob1.tag == Sdollar) {
332: s = KopString(eob1);
333: fp2fputs(byteArrayToUrlEncoding((unsigned char *)s, strlen(s)),fp);
334: }else if (eob1.tag == SbyteArray) {
335: fp2fputs(byteArrayToUrlEncoding(KopByteArray(eob1),getByteArraySize(eob1)),fp);
336: }else{
337: errorKan1("%s\n","Value is not string nor byte array.");
338: }
339: if (i < n-1) fp2fputc('&',fp);
340: }
341: }
342: return(fp2fflush(fp));
343: }
344:
345: static struct object rStringToObj(char *s,int vstart,int vend,int mode) {
346: /* mode has not yet been used. */
1.9 ! takayama 347: struct object rob = OINIT;
1.2 takayama 348: char *sss; int i;
349: int bytearray;
350: bytearray=0;
351: if (vend < vstart) return NullObject;
352: for (i=vstart; i<= vend; i++) {
353: if (s[i] == 0) bytearray=1;
354: }
355: if (bytearray) {
356: rob = newByteArrayFromStr(&(s[vstart]),vend-vstart+1);
357: return(rob);
358: }
359: sss = (char *)sGC_malloc(vend-vstart+1);
360: if (sss == NULL) errorKan1("%s\n","No more memory.");
361: for (i=vstart; i<=vend; i++) {
362: sss[i-vstart] = s[i]; sss[i-vstart+1] = 0;
363: }
364: rob = KpoString(sss);
365: return(rob);
366: }
367:
368: /*
369: [["Content-Body$,"Body"],
370: ["key1","value1"],
371: ["key2","value2"],
372: ...
373: ]
374: */
375: struct object cgiHttpToKeyValuePair(char *s,int size) {
376: int ssize,i,j,k;
377: int nOfPairs, startbody,state, kstart,kend,vstart, vend,startline,endline;
378: int nextstart,path;
1.9 ! takayama 379: struct object rob = OINIT;
! 380: struct object ob = OINIT;
1.2 takayama 381: ssize = strlen(s);
382: nOfPairs = 0; startbody = -1;
383: /* state==0 : readline and set startline and endline; state = 1;
384: state==1 : if the line is empty, then state=10;
385: Determine kstart,kend (key) and vstart,vend (value); state=0;
386: state==10 : Read the body.
387: */
388: for (path=0; path<2; path++) {
389: if (path == 1) {
390: rob = newObjectArray(nOfPairs+1); nOfPairs = 0;
391: }
392: i = 0; state=0;
393: while (i<size) {
394: if (state == 0) {
395: /* Read the line */
396: startline=i; endline= size-1; nextstart = size;
397: for (j = startline; j<size; j++) {
398: if (s[j] == 0xd) {
399: endline = j-1;
400: if (s[j+1] == 0xa) nextstart = j+2;
401: else nextstart = j+1;
402: break;
403: }else if (s[j] == 0xa) {
404: endline = j-1; nextstart = j+1; break;
405: }
406: }
407: state = 1; i = nextstart;
408: }else if (state == 1) {
409: if (endline <= startline) { state=10; }
410: else {
411: kstart=startline; kend = endline;
412: vstart=endline+1; vend = endline;
413: for (j=startline; j<=endline; j++) {
414: if (s[j] > ' ') {kstart = j; break;}
415: }
416: for (j=kstart; j<=endline; j++) {
417: if (s[j] == ':') { kend=j-1; break; }
418: }
419: for (j=kend+2; j<=endline; j++) {
420: if (s[j] > ' ') {vstart = j; break; }
421: }
422: for (j=vend; j >= vstart; j--) {
423: if (s[j] > ' ') { vend=j; break;}
424: else vend = j-1;
425: }
426: /* ppp(s,kstart,kend,vstart,vend); */
427: nOfPairs++;
428: if (path == 1) {
429: ob = newObjectArray(2);
430: putoa(ob,0,rStringToObj(s,kstart,kend,0));
431: putoa(ob,1,rStringToObj(s,vstart,vend,0));
432: putoa(rob,nOfPairs,ob);
433: }
434: state = 0;
435: }
436: }else {
437: startbody = i;
438: if (path == 1) {
439: ob = newObjectArray(2);
440: putoa(ob,0,KpoString("Content-Body"));
441: putoa(ob,1,rStringToObj(s,startbody,size-1,0));
442: putoa(rob,0,ob);
443: }
444: break;
445: }
446: }
447: }
448: return rob;
449: }
450:
451: char *cgiKeyValuePairToHttp(struct object ob,int *sizep) {
452: char *s;
453: FILE2 *fp;
454: int size;
455: fp=fp2open(-1);
456: cgiKeyValuePairToHttpFile2(ob,fp);
457: s = fp2fcloseInString(fp,sizep);
458: return(s);
459: }
460:
461: int cgiKeyValuePairToHttpFile2(struct object ob,FILE2 *fp) {
462: int n,i;
1.9 ! takayama 463: struct object eob = OINIT;
! 464: struct object eob0 = OINIT;
! 465: struct object eob1 = OINIT;
1.2 takayama 466: char *key, *s;
467: checkKeyValuePairFormat(ob,"cgiKeyValuePairToHttpFile2");
468: n = getoaSize(ob);
469: if (n == 0) return(0);
470: for (i=1; i<n; i++) {
471: eob = getoa(ob,i);
472: eob0 = getoa(eob,0); eob1 = getoa(eob,1);
473: key = KopString(eob0);
474: fp2fputs(key,fp); fp2fputs(": ",fp);
475: if (eob1.tag == Snull) ;
476: else if (eob1.tag == Sdollar) {
477: s = KopString(eob1);
478: fp2fputs(s,fp);
479: }else{
480: errorKan1("%s\n","Value is not a string.");
481: }
482: if (i < n-1) fp2fputc('\n',fp);
483: else fp2fputs("\n\n",fp);
484: }
485: eob = getoa(ob,0);
486: eob0 = getoa(eob,0); eob1 = getoa(eob,1);
487: key = KopString(eob0);
488: if (strcmp(key,"Content-Body") != 0) warningKan("Key word should be Content-Body.\n");
489: if (eob1.tag == Sdollar) {
490: fp2fputs(KopString(eob1),fp);
491: }else if (eob1.tag == SbyteArray) {
492: fp2write(fp,KopByteArray(eob1),getByteArraySize(eob1));
493: }else errorKan1("%s\n","BODY must be a string or a byte array.");
494:
495: return(fp2fflush(fp));
496: }
497:
498:
499: static test3() {
500: char *s;
1.9 ! takayama 501: struct object ob = OINIT;
1.2 takayama 502: s = "Pragma: no-cache\nContent-Length: 2915\nContent-Type: text/html\nConnection: close\n\n <DIV class=Section1> \n <P class=MsoNormal \n style=\"mso-list: none; mso-list-ins: \" 19991102T2025\"> \n </P> ";
503:
504: ob=cgiHttpToKeyValuePair(s,strlen(s));
505: printObject(ob,1,stdout);
506:
507: s = "Pragma:\nContent-Length: 2915\r\nContent-Type: text/html\nConnection: close\n\n <DIV class=Section1> \n <P class=MsoNormal \n style=\"mso-list: none; mso-list-ins: \" 19991102T2025\"> \n </P> ";
508: ob=cgiHttpToKeyValuePair(s,strlen(s));
509: printObject(ob,1,stdout);
510:
511: s = "Pragma\nContent-Length 2915\r\nContent-Type text/html\nConnection: close\n\n <DIV class=Section1> \n <P class=MsoNormal \n style=\"mso-list: none; mso-list-ins: \" 19991102T2025\"> \n </P> ";
512: ob=cgiHttpToKeyValuePair(s,strlen(s));
513: printObject(ob,1,stdout);
514:
515: {
516: char *s;
517: s = "This is a pen.com? !@#0989\n";
518: printf("\n%s\n",byteArrayToUrlEncoding((unsigned char *)s,strlen(s)));
519: }
520: }
521:
522: struct object cgiKeyValuePairToUrlEncodingString(struct object ob) {
523: char *s;
524: s = cgiKeyValuePairToUrlEncoding(ob);
525: if (s == NULL) return NullObject;
526: return KpoString(s);
527: }
528: struct object cgiKeyValuePairToHttpString(struct object ob) {
529: int size;
530: char *s;
531: s = cgiKeyValuePairToHttp(ob,&size);
532: if (s == NULL) return NullObject;
533: return KpoString(s);
534: }
535:
1.3 takayama 536: struct object KooStringToUrlEncoding(struct object sob) {
537: unsigned char *s;
538: char *rs;
539: int n;
540: if (sob.tag == Sdollar) {
541: s = (unsigned char *) KopString(sob);
542: n = strlen((char *)s);
543: }else if (sob.tag == SbyteArray) {
544: s = KopByteArray(sob);
545: n = getByteArraySize(sob);
546: }else errorKan1("%s\n","KooStringToUrlEncoding: argument must be a string or a bytearray.");
547: rs = byteArrayToUrlEncoding(s,n);
548: return KpoString(rs);
549: }
550:
551: struct object KooUrlEncodedStringToObj(struct object sob) {
552: char *s;
553: int n;
554: if (sob.tag == Sdollar) {
555: s = KopString(sob);
556: n = strlen((char *)s);
557: }else if (sob.tag == SbyteArray) {
558: s = KopByteArray(sob);
559: n = getByteArraySize(sob);
560: }else errorKan1("%s\n","KooUrlEncodedStringToObj: argument must be a string.");
561: return urlEncodedStringToObj(s,0,n-1,0);
1.8 takayama 562: }
563:
564: static struct object toTokens(char *s,int *sep,int nsep) {
565: /* s is the input, and sep are the separators. */
566: /* -1 means <=' ' are separators */
567: int nOfTokens,n,i,done,k,start,sav;
1.9 ! takayama 568: struct object rob = OINIT;
1.8 takayama 569: char *t;
570:
571: rob = NullObject;
572: if (nsep < 1) return rob;
573: if (sep[0] != -1) {
574: fprintf(stderr,"cgiToTokens: Not implemeted for this separator.\n");
575: return rob;
576: }
577:
578: /* Count the number of tokens */
579: n = strlen(s); i = 0; nOfTokens=0;
580: while (i < n) {
581: done = 0;
582: while (s[i] <= ' ') {
583: i++; if (i >= n) { done=1; break;}
584: }
585: if (done==1) break;
586: nOfTokens++;
587: while (s[i] > ' ') {
588: i++; if (i >= n) { done=1; break; }
589: }
590: if (done == 1) break;
591: }
592:
593: rob = newObjectArray(nOfTokens);
594: n = strlen(s); i = 0; k = 0;
595: while (i < n) {
596: done = 0;
597: while (s[i] <= ' ') {
598: i++; if (i >= n) { done=1; break;}
599: }
600: if (done==1) break;
601: start = i;
602: while (s[i] > ' ') {
603: i++; if (i >= n) { done=1; break; }
604: }
605: t = (char *) GC_malloc(i-start+1);
606: if (t == NULL) { fprintf(stderr,"No more memory.\n"); exit(10); }
607: t[i-start] = 0;
608: strncpy(t,&(s[start]),i-start);
609: putoa(rob,k,KpoString(t));
610: k++;
611: if (done == 1) break;
612: }
613:
614: return rob;
615: }
616:
617: struct object KooToTokens(struct object ob,struct object sep) {
618: char *s;
619: int n;
620: int tmp[1];
621: tmp[0] = -1;
622: if (ob.tag == Sdollar) {
623: s = KopString(ob);
624: n = strlen((char *)s);
625: }else errorKan1("%s\n","KooToTokens: the first argument must be a string.");
626: if (sep.tag == Sarray) {
627: if (getoaSize(sep) != 0) {
628: errorKan1("%s\n","This separators have not been implemented.");
629: }
630: }else errorKan1("%s\n","KooToTokens: the second argument(separators) must be an array.");
631: return toTokens(s,tmp,1);
1.3 takayama 632: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>