/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * ident "%Z%%M% %I% %E% SMI" * * Copyright (c) 1999 by Sun Microsystems, Inc. * All rights reserved. * */ // SCCS Status: %W% %G% // SLPV1SSrvMsg.java: SLPv1 server side service rqst/reply. // Author: James Kempf // Created On: Thu Sep 10 15:33:58 1998 // Last Modified By: James Kempf // Last Modified On: Fri Nov 6 14:03:00 1998 // Update Count: 41 // package com.sun.slp; import java.util.*; import java.io.*; /** * The SLPV1SSrvMsg class models the SLP server side service request message. * * @version %R%.%L% %D% * @author James Kempf */ class SLPV1SSrvMsg extends SSrvMsg { // For eating whitespace. final static char SPACE = ' '; // Comma for list parsing. final static char COMMA = ','; // Logical operators. final static char OR_OP = '|'; final static char AND_OP = '&'; // Logical operator corner case needs this. final static char HASH = '#'; // Comparison/Assignment operators. final static char EQUAL_OP = '='; final static char NOT_OP = '!'; final static char LESS_OP = '<'; final static char GREATER_OP = '>'; final static char GEQUAL_OP = 'g'; final static char LEQUAL_OP = 'l'; // Parens. final static char OPEN_PAREN = '('; final static char CLOSE_PAREN = ')'; // LDAP present operator final static char PRESENT = '*'; // Wildcard operator. final static String WILDCARD = "*"; // Character code for parsing. String charCode = IANACharCode.UTF8; // For creating a null reply. protected SLPV1SSrvMsg() {} // Construct a SLPV1SSrvMsg from the input stream. SLPV1SSrvMsg(SrvLocHeader hdr, DataInputStream dis) throws ServiceLocationException, IOException { super(hdr, dis); } // Construct an empty SLPV1SSrvMsg, for monolingual off. static SrvLocMsg makeEmptyReply(SLPHeaderV1 hdr) throws ServiceLocationException { SLPV1SSrvMsg msg = new SLPV1SSrvMsg(); msg.hdr = hdr; msg.makeReply(new Hashtable(), null); return msg; } // Initialize the message from the input stream. void initialize(DataInputStream dis) throws ServiceLocationException, IOException { SLPHeaderV1 hdr = (SLPHeaderV1)getHeader(); StringBuffer buf = new StringBuffer(); // First get the previous responder. hdr.parsePreviousRespondersIn(dis); // Now get the raw query. hdr.getString(buf, dis); String rq = buf.toString(); // Parse the raw query to pull out the service type, scope, // and query. StringTokenizer st = new StringTokenizer(rq, "/", true); try { String type = Defaults.SERVICE_PREFIX + ":" + st.nextToken().trim().toLowerCase() + ":"; serviceType = hdr.checkServiceType(type); st.nextToken(); // get rid of slash. // Get the scope. String scope = st.nextToken().trim().toLowerCase(); // Special case if scope is empty (meaning the next // token will be a slash). if (scope.equals("/")) { scope = ""; } else { st.nextToken(); // get rid of slash. if (scope.length() > 0) { // Validate the scope name. hdr.validateScope(scope); } } // Set up scopes vector. hdr.scopes = new Vector(); // Substitute default scope here. if (scope.length() <= 0) { scope = Defaults.DEFAULT_SCOPE; } hdr.scopes.addElement(scope.toLowerCase().trim()); // Parsing the query is complicated by opaques having slashes. String q = ""; while (st.hasMoreTokens()) { q = q + st.nextToken(); } // Drop off the final backslash, error if none. if (!q.endsWith("/")) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {rq}); } query = q.substring(0, q.length()-1); // Save header char code for parsing. charCode = hdr.charCode; // Convert the query into a V2 query. convertQuery(); // If the query is for "service:directory-agent", then we // mark it as having been multicast, because that is the // only kind of multicast that we accept for SLPv1. Anybody // who unicasts this to us will time out. if (serviceType.equals(Defaults.DA_SERVICE_TYPE.toString())) { hdr.mcast = true; } // Construct description. hdr.constructDescription("SrvRqst", " service type=``" + serviceType + "''\n" + " query=``" + query + "''"); } catch (NoSuchElementException ex) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {rq}); } } // Make a reply message. SrvLocMsg makeReply(Hashtable urltable, Hashtable URLSignatures) throws ServiceLocationException { SLPHeaderV1 hdr = ((SLPHeaderV1)getHeader()).makeReplyHeader(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Edit out abstract types and nonService: URLs. Enumeration en = urltable.keys(); Vector urls = new Vector(); while (en.hasMoreElements()) { ServiceURL surl = (ServiceURL)en.nextElement(); // Reject if abstract type or nonservice: URL. ServiceType type = surl.getServiceType(); if (!type.isAbstractType() && type.isServiceURL()) { urls.addElement(surl); } } hdr.iNumReplies = urls.size(); // keep this info so SAs can drop 0 replies int n = urls.size(); // Write out the size of the list. hdr.putInt(n, baos); en = urls.elements(); // Write out the size of the list. while (en.hasMoreElements()) { ServiceURL surl = (ServiceURL)en.nextElement(); hdr.parseServiceURLOut(surl, true, baos); } // We ignore the signatures because we only do V1 compatibility // for nonprotected scopes. hdr.payload = baos.toByteArray(); hdr.constructDescription("SrvRply", " service URLs=``" + urls + "''\n"); return hdr; } // Convert the query to a V2 query. void convertQuery() throws ServiceLocationException { // Check for empty query. query = query.trim(); if (query.length() <= 0) { return; } // Check for query join. if (!(query.startsWith("(") && query.endsWith(")"))) { // Rewrite to a standard query. query = rewriteQueryJoin(query); } // Now rewrite the query into v2 format. query = rewriteQuery(query); } // Rewrite a query join as a conjunction. private String rewriteQueryJoin(String query) throws ServiceLocationException { // Turn infix expression into prefix. StringBuffer sbuf = new StringBuffer(); StringTokenizer tk = new StringTokenizer(query, ",", true); boolean lastTokComma = true; int numEx = 0; while (tk.hasMoreElements()) { String exp = tk.nextToken().trim(); if (exp.equals(",")) { if (lastTokComma) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } else { lastTokComma = true; } } else { lastTokComma = false; if (exp.length() <= 0) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Put in parens sbuf.append("("); sbuf.append(exp); sbuf.append(")"); numEx++; } } if (lastTokComma || numEx == 0) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } if (numEx > 1) { sbuf.insert(0, "(&"); sbuf.append(")"); } return sbuf.toString(); } // Rewrite a v1 query into v2 format. This includes character escaping. private String rewriteQuery(String whereList) throws ServiceLocationException { // Parse a logical expression. StreamTokenizer tk = new StreamTokenizer(new StringReader(whereList)); tk.resetSyntax(); // make all chars ordinary... tk.whitespaceChars('\000','\037'); tk.ordinaryChar(SPACE); // but beware of embedded whites... tk.wordChars('!', '%'); tk.ordinaryChar(AND_OP); tk.wordChars('\'', '\''); tk.ordinaryChar(OPEN_PAREN); tk.ordinaryChar(CLOSE_PAREN); tk.wordChars('*', '{'); tk.ordinaryChar(OR_OP); tk.wordChars('}', '~'); // Initialize parse tables in terminal. tk.ordinaryChar(EQUAL_OP); tk.ordinaryChar(NOT_OP); tk.ordinaryChar(LESS_OP); tk.ordinaryChar(GREATER_OP); StringBuffer buf = new StringBuffer(); // Parse through the expression. try { parseInternal(tk, buf, true); } catch (IOException ex) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } return buf.toString(); } // Do the actual parsing, using the passed-in stream tokenizer. private void parseInternal(StreamTokenizer tk, StringBuffer buf, boolean start) throws ServiceLocationException, IOException { int tok = 0; boolean ret = true; do { tok = eatWhite(tk); // We should be at the beginning a parenthesized // where list. if (tok == OPEN_PAREN) { // Get the next token. Eat whitespace in the process. tok = eatWhite(tk); // If it's a logOp, then process as a logical expression. // This handles the following nasty case: // // (,-==the rest of it) int logOp = tok; if (logOp == AND_OP) { // Need to check for escape as first thing. tok = tk.nextToken(); String str = tk.sval; // not used if token not a string... tk.pushBack(); if (tok == StreamTokenizer.TT_WORD) { if (str.charAt(0) != HASH) { parseLogicalExpression(logOp, tk, buf); } else { parse(tk, buf, true); // cause we can't push back twice } } else { parseLogicalExpression(logOp, tk, buf); } break; } else if (logOp == OR_OP) { parseLogicalExpression(logOp, tk, buf); break; } else { // It's a terminal expression. Push back the last token // and parse the terminal. tk.pushBack(); parse(tk, buf, false); break; } } else { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } } while (true); // Since terminals are allowed alone at the top level, // we need to check here whether anything else is // in the query. if (start) { tok = eatWhite(tk); if (tok != StreamTokenizer.TT_EOF) { // The line should have ended by now. throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } } } // Rewrite a logical expression. private void parseLogicalExpression(int logOp, StreamTokenizer tk, StringBuffer buf) throws ServiceLocationException, IOException { // Append paren and operator to buffer. buf.append((char)OPEN_PAREN); buf.append((char)logOp); int tok = 0; do { tok = eatWhite(tk); if (tok == OPEN_PAREN) { // So parseInternal() sees a parenthesized list. tk.pushBack(); // Go back to parseInternal. parseInternal(tk, buf, false); } else if (tok == CLOSE_PAREN) { // Append the character to the buffer and return. buf.append((char)tok); return; } else { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } } while (tok != StreamTokenizer.TT_EOF); // Error if we've not caught ourselves before this. throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Parse a terminal. Opening paren has been got. private void parse(StreamTokenizer tk, StringBuffer buf, boolean firstEscaped) throws ServiceLocationException, IOException { String tag = ""; int tok = 0; tok = eatWhite(tk); // Gather the tag and value. if (tok != StreamTokenizer.TT_WORD) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Parse the tag. tag = parseTag(tk, firstEscaped); if (tag.length() <= 0) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Unescape tag. tag = ServiceLocationAttributeV1.unescapeAttributeString(tag, charCode); // Now escape in v2 format, tag = ServiceLocationAttribute.escapeAttributeString(tag, true); // Parse the operator. char compOp = parseOperator(tk); // If this was a keyword operator, then add present // operator and closing paren and return. if (compOp == PRESENT) { buf.append(OPEN_PAREN); buf.append(tag); buf.append(EQUAL_OP); buf.append(PRESENT); buf.append(CLOSE_PAREN); return; } // Parse value by reading up to the next close paren. // Returned value will be in v2 format. String valTok = parseValue(tk); // Construct the comparision depending on the operator. if (compOp == NOT_OP) { // If the value is an integer, we can construct a query // that will exclude the number. try { int n = Integer.parseInt(valTok); // Bump the integer up and down to catch numbers on both // sides of the required number. Be careful not to // overstep bounds. if (n < Integer.MAX_VALUE) { buf.append(OPEN_PAREN); buf.append(tag); buf.append(GREATER_OP); buf.append(EQUAL_OP); buf.append(n + 1); buf.append(CLOSE_PAREN); } if (n > Integer.MIN_VALUE) { buf.append(OPEN_PAREN); buf.append(tag); buf.append(LESS_OP); buf.append(EQUAL_OP); buf.append(n - 1); buf.append(CLOSE_PAREN); } if ((n < Integer.MAX_VALUE) && (n > Integer.MIN_VALUE)) { buf.insert(0, OR_OP); buf.insert(0, OPEN_PAREN); buf.append(CLOSE_PAREN); } } catch (NumberFormatException ex) { // It's not an integer. We can construct a query expression // that will not always work. The query rules out advertisments // where the attribute value doesn't match and there are // no other attributes or values, and advertisements // that don't contain the attribute, but it doesn't rule out // a multivalued attribute with other values or if there // are other attributes. The format of the query is: // "(&(=*)(!(=))). buf.append(OPEN_PAREN); buf.append(AND_OP); buf.append(OPEN_PAREN); buf.append(tag); buf.append(EQUAL_OP); buf.append(PRESENT); buf.append(CLOSE_PAREN); buf.append(OPEN_PAREN); buf.append(NOT_OP); buf.append(OPEN_PAREN); buf.append(tag); buf.append(EQUAL_OP); buf.append(valTok); buf.append(CLOSE_PAREN); buf.append(CLOSE_PAREN); buf.append(CLOSE_PAREN); } } else if ((compOp == LESS_OP) || (compOp == GREATER_OP)) { int n = 0; try { n = Integer.parseInt(valTok); } catch (NumberFormatException ex) { // It's a parse error here. throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // We don't attempt to handle something that would cause // arithmetic overflow. if ((n == Integer.MAX_VALUE) || (n == Integer.MIN_VALUE)) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Construct a query that includes everything // to the correct side. buf.append(OPEN_PAREN); buf.append(tag); if (compOp == LESS_OP) { buf.append(LESS_OP); buf.append(EQUAL_OP); buf.append(n - 1); } else { buf.append(GREATER_OP); buf.append(EQUAL_OP); buf.append(n + 1); } buf.append(CLOSE_PAREN); } else { // Simple, single operator. Just add it with the // value. buf.append(OPEN_PAREN); buf.append(tag); // Need to distinguish less and greater equal. if (compOp == LEQUAL_OP) { buf.append(LESS_OP); buf.append(EQUAL_OP); } else if (compOp == GEQUAL_OP) { buf.append(GREATER_OP); buf.append(EQUAL_OP); } else { buf.append(compOp); } buf.append(valTok); buf.append(CLOSE_PAREN); } } // Gather tokens with embedded whitespace and return. private String parseTag(StreamTokenizer tk, boolean ampStart) throws ServiceLocationException, IOException { String value = ""; // Take care of corner case here. if (ampStart) { value = value +"&"; ampStart = false; } do { if (tk.ttype == StreamTokenizer.TT_WORD) { value += tk.sval; } else if ((char)tk.ttype == SPACE) { value = value + " "; } else if ((char)tk.ttype == AND_OP) { value = value + "&"; } else { break; } tk.nextToken(); } while (true); return value.trim(); // removes trailing whitespace... } private char parseOperator(StreamTokenizer tk) throws ServiceLocationException, IOException { int tok = tk.ttype; // If the token is a close paren, then this was a keyword // (e.g. "(foo)". Return the present operator. if ((char)tok == CLOSE_PAREN) { return PRESENT; } if (tok != EQUAL_OP && tok != NOT_OP && tok != LESS_OP && tok != GREATER_OP) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } char compOp = (char)tok; // Get the next token. tok = tk.nextToken(); // Look for dual character operators. if ((char)tok == EQUAL_OP) { // Here, we can have either "!=", "<=", ">=", or "==". // Anything else is wrong. if (compOp != LESS_OP && compOp != GREATER_OP && compOp != EQUAL_OP && compOp != NOT_OP) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } // Assign the right dual operator. if (compOp == LESS_OP) { compOp = LEQUAL_OP; } else if (compOp == GREATER_OP) { compOp = GEQUAL_OP; } } else if (compOp != LESS_OP && compOp != GREATER_OP) { // Error if the comparison operator was something other // than ``<'' or ``>'' and there is no equal. This // rules out ``!'' or ``='' alone. throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } else { // Push back the last token if it wasn't a two character operator. tk.pushBack(); } return compOp; } private String parseValue(StreamTokenizer tk) throws ServiceLocationException, IOException { int tok = 0; StringBuffer valTok = new StringBuffer(); // Eat leading whitespace. tok = eatWhite(tk); // If the first value is a paren, then we've got an // opaque. if ((char)tok == OPEN_PAREN) { valTok.append("("); // Collect all tokens up to the closing paren. do { tok = tk.nextToken(); // It's a closing paren. break out of the loop. if ((char)tok == CLOSE_PAREN) { valTok.append(")"); break; } else if ((char)tok == EQUAL_OP) { valTok.append("="); } else if (tok == StreamTokenizer.TT_WORD) { valTok.append(tk.sval); } else { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } } while (true); // Eat whitespace until closing paren. tok = eatWhite(tk); if ((char)tok != CLOSE_PAREN) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } } else { // Error if just a closed paren. if (tok == CLOSE_PAREN) { throw new ServiceLocationException( ServiceLocationException.PARSE_ERROR, "v1_query_error", new Object[] {query}); } do { // Append the token if a WORD if (tok == StreamTokenizer.TT_WORD) { valTok.append(tk.sval); } else if ((tok != StreamTokenizer.TT_EOF) && (tok != StreamTokenizer.TT_EOL) && (tok != CLOSE_PAREN)) { // Otherwise, it's a token char, so append. valTok.append((char)tok); } tok = tk.nextToken(); } while (tok != CLOSE_PAREN); } // If a wildcard, remove wildcard stars here for later re-insertion. String strval = valTok.toString().trim(); boolean wildstart = false; boolean wildend = false; if (strval.startsWith(WILDCARD)) { wildstart = true; strval = strval.substring(1, strval.length()); } if (strval.endsWith(WILDCARD)) { wildend = true; strval = strval.substring(0, strval.length()-1); } // Evaluate the value. Object val = ServiceLocationAttributeV1.evaluate(strval, charCode); // Now convert to v2 format, and return. if (val instanceof String) { strval = ServiceLocationAttribute.escapeAttributeString(val.toString(), false); // Add wildcards back in. if (wildstart) { strval = WILDCARD + strval; } if (wildend) { strval = strval + WILDCARD; } } else { strval = val.toString(); } return strval; } // Eat whitespace. private int eatWhite(StreamTokenizer tk) throws IOException { int tok = tk.nextToken(); while (tok == SPACE) { tok = tk.nextToken(); } return tok; } }