/* * Dictionary Server Protocol client (RFC2229) */ #include #include #include #include #pragma varargck type "D" char* #pragma varargck argpos sendcmd 1 #pragma varargck argpos simplecmd 1 typedef struct Resp Resp; struct Resp { /* Response */ int type; char *line; int code; char msg[1024]; /* everything but response code */ int n; char *word; char *db; char *dbdescr; }; enum { Rstatus = 1, /* status response */ Rtext, /* text response */ EOT /* End of Text marker */ }; Biobuf bin, bout; char *addr; char *database = "*"; char *strat = "."; int domatch; int showdb; int showstrat; int showserver; int showinfo; char* dbquote(char *s) /* double quote a string */ { char *q, *r, *t; /* * max size occurs when escaping each char * + 2 quotes surrounding it + null char */ q = malloc(2*strlen(s)+2+1); r = q; *r++ = '"'; for(t = s; *t; t++) switch(*t){ case '\\': case '"': *r++ = '\\'; *r++ = *t; break; default: *r++ = *t; } *r++ = '"'; *r = 0; return q; } int Dfmt(Fmt *f) { char *s, *q; int n; s = va_arg(f->args, char*); q = dbquote(s); n = fmtprint(f, "%s", q); free(q); return n; } /* * unquote a string quoted using quote char qc */ char* unquote(char qc, char *s) { char *t; *s++ = 0; /* remove quote char */ for(t = s; *t; t++){ if(t[0] == '\\' && t[1] == '\\') t++; else if(t[0] == '\\' && t[1] == qc) t++; else if(t[0] == qc) break; *s++ = *t; } if(t[0] == 0 || t[1] == 0) t = nil; /* end of input string */ else t++; /* after the quoted string */ *s = 0; return t; } int qtokenize(char *s, char **args, int maxargs) { int i; if(*s == 0) return 0; for(i = 0; i < maxargs; i++){ args[i] = s; if(*s == '"' || *s == '\''){ args[i] = &s[1]; s = unquote(*s, s); if(s == nil) return i+1; *s++ = 0; /* skip space */ }else{ s = strchr(s, ' '); if(s == nil) return i+1; *s++ = 0; /* skip space */ } } return i; } char* getline(void) { char *s; int n; s = Brdline(&bin, '\n'); if(s != nil){ n = Blinelen(&bin); s[n-2] = 0; } return s; } void sendcmd(char *fmt, ...) { va_list args; va_start(args, fmt); Bvprint(&bout, fmt, args); va_end(args); Bprint(&bout, "\r\n"); Bflush(&bout); } Resp* getresp(void) { static Resp response; Resp *r; char *arg[4], *line; int n; r = &response; memset(r, 0, sizeof(*r)); r->line = line = getline(); if(isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2])) r->type = Rstatus; else{ if(line[0] == '.' && line[1] == 0) r->type = EOT; else if(strncmp(line, "..", 3) == 0){ r->type = Rtext; r->line[1] = 0; }else r->type = Rtext; return r; } /* * Process status response */ utfecpy(r->msg, r->msg+sizeof(r->msg), &line[(line[3]==0) ? 3 : 4]); n = qtokenize(line, arg, nelem(arg)); r->code = atoi(arg[0]); switch(r->code){ case 110: case 111: case 150: case 152: if(n < 2) return r; r->n = atoi(arg[1]); break; case 151: if(n < 4) return r; r->word = arg[1]; r->db = arg[2]; r->dbdescr = arg[3]; break; } return r; } void printtext(void) { Resp *r; for(;;){ r = getresp(); if(r->type == EOT) break; print("%s\n", r->line); } } void simplecmd(char *fmt, ...) { Resp *r; int e; va_list args; va_start(args, fmt); Bvprint(&bout, fmt, args); va_end(args); Bprint(&bout, "\r\n"); Bflush(&bout); r = getresp(); e = r->code/100; if(r->type == Rstatus && e != 5 && e != 4){ printtext(); getresp(); /* 250 */ }else sysfatal("bad response: %s", r->msg); } void match(char *db, char *strat, char *query) { Resp *r; char *f[2]; int n, e; doquote = needsrcquote; quotefmtinstall(); sendcmd("MATCH %s %s %D", db, strat, query); r = getresp(); if(r->type == Rstatus && r->code == 552){ print("no match found\n"); return; } e = r->code/100; if(r->type != Rstatus || e == 5 || e == 4) sysfatal("bad response: %s", r->msg); for(;;){ r = getresp(); if(r->type == EOT) break; n = qtokenize(r->line, f, nelem(f)); if(n != 2) sysfatal("bad match output: %s", r->line); print("dic -d %s %q\n", f[0], f[1]); } } void define(char *db, char *word) { Resp *r; sendcmd("DEFINE %s %D", db, word); r = getresp(); if(r->type == Rstatus && r->code == 552){ /* no match */ match(database, strat, word); return; } if(r->type != Rstatus || r->code != 150) sysfatal("bad response: %s", r->msg); for(;;){ r = getresp(); if(r->type == Rstatus && r->code != 151) break; print("From %s [%s]:\n\n", r->dbdescr, r->db); printtext(); print("\n"); } if(r->code != 250) sysfatal("command failed: %s", r->msg); } void usage(void) { fprint(2, "usage: dic [-DISim] [-a addr] [-d db] [-s strategy] word\n"); exits("usage"); } void main(int argc, char *argv[]) { int fd; Resp *r; fmtinstall('D', Dfmt); ARGBEGIN { case 'D': showdb++; break; case 'S': showstrat++; break; case 'I': showserver++; break; case 'i': showinfo++; break; case 'a': addr = EARGF(usage()); break; case 'd': database = EARGF(usage()); break; case 's': strat = EARGF(usage()); /* fall through */ case 'm': domatch++; break; default: usage(); } ARGEND if(!showdb && !showstrat && !showserver && argc < 1) usage(); if(addr) fd = dial(netmkaddr(addr, "tcp", "2628"), nil, nil, nil); else{ fd = dial("tcp!localhost!2628", nil, nil, nil); if(fd < 0) fd = dial("tcp!dict.org!2628", nil, nil, nil); } if(fd < 0) sysfatal("dial: %r"); Binit(&bin, fd, OREAD); Binit(&bout, fd, OWRITE); r = getresp(); if(r->type != Rstatus || r->code != 220) sysfatal("server not usable: %s", r->msg); sendcmd("CLIENT plan9/dic"); getresp(); /* ignore reply */ if(showdb) simplecmd("SHOW DB"); else if(showstrat) simplecmd("SHOW STRAT"); else if(showserver) simplecmd("SHOW SERVER"); else{ if(domatch) match(database, strat, argv[0]); else if(showinfo) simplecmd("SHOW INFO %s", argv[0]); else define(database, argv[0]); } sendcmd("QUIT"); Bterm(&bin); Bterm(&bout); close(fd); exits(nil); }