001: /*
002: * $Id: MimeHeaderCanonicalizer.java,v 1.5 2007/01/08 16:06:14 shyam_rao Exp $
003: */
004:
005: /*
006: * The contents of this file are subject to the terms
007: * of the Common Development and Distribution License
008: * (the License). You may not use this file except in
009: * compliance with the License.
010: *
011: * You can obtain a copy of the license at
012: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
013: * See the License for the specific language governing
014: * permissions and limitations under the License.
015: *
016: * When distributing Covered Code, include this CDDL
017: * Header Notice in each file and include the License file
018: * at https://glassfish.dev.java.net/public/CDDLv1.0.html.
019: * If applicable, add the following below the CDDL Header,
020: * with the fields enclosed by brackets [] replaced by
021: * you own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
025: */
026:
027: package com.sun.xml.wss.impl.c14n;
028:
029: import java.util.Vector;
030: import java.util.Iterator;
031: import java.util.StringTokenizer;
032:
033: import javax.xml.soap.MimeHeader;
034:
035: import com.sun.xml.wss.swa.MimeConstants;
036: import com.sun.xml.wss.XWSSecurityException;
037: import java.io.InputStream;
038: import java.io.OutputStream;
039:
040: import javax.mail.internet.MimeUtility;
041:
042: public class MimeHeaderCanonicalizer extends Canonicalizer {
043:
044: public MimeHeaderCanonicalizer() {
045: }
046:
047: public byte[] canonicalize(byte[] input)
048: throws XWSSecurityException {
049: return null;
050: }
051:
052: public InputStream canonicalize(InputStream input,
053: OutputStream outputStream)
054: throws javax.xml.crypto.dsig.TransformException {
055: throw new UnsupportedOperationException();
056: }
057:
058: public byte[] _canonicalize(Iterator mimeHeaders /* internal class, therefore ok */)
059: throws XWSSecurityException {
060: String _mh = "";
061:
062: // rf. steps 2-10 Section 4.3.1 SwA Draft 13
063: // SAAJ RI returns trimmed (at start) Content-Description values
064: // Unstructured MIME Headers can be RFC2047 encoded, step 6
065: // Unfold, step 5; Uncomment, step 8; Steps 9-16 not applicable
066: String cDescription = getMatchingHeader(mimeHeaders,
067: MimeConstants.CONTENT_DESCRIPTION);
068: if (cDescription != null) {
069: _mh += MimeConstants.CONTENT_DESCRIPTION + _CL;
070: _mh += uncomment(rfc2047decode(unfold(cDescription)));
071: _mh += _CRLF;
072: }
073:
074: // Unfold, step 5; unfold WSP, step 7;
075: String cDisposition = getMatchingHeader(mimeHeaders,
076: MimeConstants.CONTENT_DISPOSITION);
077: if (cDisposition != null) {
078: _mh += MimeConstants.CONTENT_DISPOSITION + _CL;
079: _mh += canonicalizeHeaderLine(
080: uncomment(unfold(cDisposition)), true);
081: _mh += _CRLF;
082: }
083:
084: // Unfold, step 5; unfold WSP, step 7;
085: String cId = getMatchingHeader(mimeHeaders,
086: MimeConstants.CONTENT_ID);
087: if (cId != null) {
088: _mh += MimeConstants.CONTENT_ID + _CL;
089: _mh += unfoldWS(uncomment(unfold(cId))).trim();
090: _mh += _CRLF;
091: }
092:
093: // Unfold, step 5; unfold WSP, step 7;
094: String cLocation = getMatchingHeader(mimeHeaders,
095: MimeConstants.CONTENT_LOCATION);
096: if (cLocation != null) {
097: _mh += MimeConstants.CONTENT_LOCATION + _CL;
098: _mh += unfoldWS(uncomment(unfold(cLocation))).trim();
099: _mh += _CRLF;
100: }
101:
102: // Unfold, step 5; unfold WSP, step 7;
103: String cType = getMatchingHeader(mimeHeaders,
104: MimeConstants.CONTENT_TYPE);
105: cType = (cType == null) ? "text/plain; charset=us-ascii"
106: : cType;
107:
108: _mh += MimeConstants.CONTENT_TYPE + _CL;
109: _mh += canonicalizeHeaderLine(uncomment(unfold(cType)), true);
110: _mh += _CRLF;
111:
112: _mh += _CRLF; // step 17
113:
114: byte[] b = null;
115: try {
116: b = _mh.getBytes("UTF-8");
117: } catch (Exception e) {
118: // log
119: throw new XWSSecurityException(e);
120: }
121:
122: return b;
123: }
124:
125: private String getMatchingHeader(Iterator mimeHeaders, String key)
126: throws XWSSecurityException {
127: String header_line = null;
128: try {
129: /* for (int i=0; i<mimeHeaders.size(); i++) {
130: MimeHeader mhr = (MimeHeader)mimeHeaders.elementAt(i);
131:
132: if (mhr.getName().equalsIgnoreCase(key)) {
133: header_line = mhr.getValue();
134: break;
135: }
136: }*/
137: while (mimeHeaders.hasNext()) {
138: MimeHeader mhr = (MimeHeader) mimeHeaders.next();
139: if (mhr.getName().equalsIgnoreCase(key)) {
140: header_line = mhr.getValue();
141: break;
142: }
143: }
144: } catch (Exception npe) {
145: // log
146: throw new XWSSecurityException(
147: "Failed to locate MIME Header, " + key);
148: }
149: return header_line;
150: }
151:
152: private String unfold(String input) {
153: if (input.charAt(0) == _QT.charAt(0)
154: || input.charAt(input.length() - 1) == _QT.charAt(0))
155: return input;
156:
157: // remove all CRLF sequences
158: StringBuffer sb = new StringBuffer();
159:
160: for (int i = 0; i < input.length(); i++) {
161: char c = input.charAt(i);
162: if (c == _CRLF.charAt(0) && i != input.length() - 1)
163: if (input.charAt(i + 1) == _CRLF.charAt(1)) {
164: i++;
165: continue;
166: }
167: sb.append(c);
168: }
169:
170: return sb.toString();
171: }
172:
173: private String rfc2047decode(String input)
174: throws XWSSecurityException {
175: if (input.charAt(0) == _QT.charAt(0)
176: || input.charAt(input.length() - 1) == _QT.charAt(0))
177: return input;
178:
179: String decodedText = null;
180: try {
181: decodedText = MimeUtility.decodeText(input);
182: } catch (Exception e) {
183: // log
184: throw new XWSSecurityException(e);
185: }
186: return decodedText;
187: }
188:
189: private String unfoldWS(String input) {
190: if (input.charAt(0) == _QT.charAt(0)
191: || input.charAt(input.length() - 1) == _QT.charAt(0))
192: return input;
193:
194: StringBuffer sb = new StringBuffer();
195: for (int i = 0; i < input.length(); i++) {
196: if (input.charAt(i) == _WS.charAt(0)
197: || input.charAt(i) == _HT.charAt(0)) {
198: sb.append(_WS.charAt(0));
199: for (i++; i != input.length() - 1; i++)
200: if (input.charAt(i) != _WS.charAt(0)
201: && input.charAt(i) != _HT.charAt(0)) {
202: sb.append(input.charAt(i));
203: break;
204: }
205: } else
206: sb.append(input.charAt(i));
207: }
208:
209: return sb.toString();
210: }
211:
212: private String uncomment(String input) {
213: if (input.charAt(0) == _QT.charAt(0)
214: || input.charAt(input.length() - 1) == _QT.charAt(0))
215: return input;
216:
217: for (int oc = 0; oc < input.length(); oc++) {
218: if ((oc = input.indexOf(_OC)) == -1)
219: // no opening brace found
220: break;
221:
222: int offset = input.substring(oc).indexOf(_CC);
223: if (offset == -1)
224: // no closing brace found
225: break;
226:
227: int cc = oc + offset;
228:
229: String fs = (oc != 0) ? input.substring(0, oc) : "";
230: String bs = input.substring(cc + 1);
231:
232: if (offset == 1) {
233: // encountered "..()"
234: input = fs + _WS + bs;
235:
236: } else {
237: if (input.substring(oc + 1, cc).indexOf(_OC) == -1) {
238: // encountered "..(..).."
239: input = fs + _WS + bs;
240: } else {
241: // encountered nested comment, bombs if comments are malformed
242: input = fs
243: + _OC
244: + uncomment(input.substring(oc + 1, cc + 1))
245: + bs;
246:
247: }
248: }
249: }
250:
251: return input;
252: }
253:
254: private String canonicalizeHeaderLine(String input,
255: boolean applyStep10SwaDraft13) throws XWSSecurityException {
256: int _sc = input.indexOf(_SC);
257: if (_sc <= 0 || _sc == input.length() - 1)
258: return input;
259:
260: // step 9 MHC SwA Draft 13
261: String _fs = input.substring(0, _sc).toLowerCase();
262: if (applyStep10SwaDraft13)
263: _fs = quote(_fs, false);
264:
265: // params found
266: String size = null;
267: String type = null;
268: String charset = null;
269: String padding = null;
270: String filename = null;
271: String read_date = null;
272: String creation_date = null;
273: String modification_date = null;
274:
275: String decoded = null;
276:
277: try {
278: decoded = rfc2184decode(input.substring(_sc + 1));
279: } catch (Exception e) {
280: // log
281: throw new XWSSecurityException(e);
282: }
283:
284: StringTokenizer strnzr = new StringTokenizer(decoded, _SC);
285: while (strnzr.hasMoreElements()) {
286: String param = strnzr.nextToken();
287:
288: String pname = param.substring(0, param.indexOf(_EQ));
289: String value = param.substring(param.indexOf(_EQ) + 1);
290:
291: if (pname.equalsIgnoreCase(MimeConstants.TYPE)) {
292: type = quote(value.toLowerCase(), true);
293: } else if (pname.equalsIgnoreCase(MimeConstants.PADDING)) {
294: padding = quote(value.toLowerCase(), true);
295: } else if (pname.equalsIgnoreCase(MimeConstants.CHARSET)) {
296: charset = quote(value.toLowerCase(), true);
297: } else if (pname.equalsIgnoreCase(MimeConstants.FILENAME)) {
298: filename = quote(value.toLowerCase(), true);
299: } else if (pname
300: .equalsIgnoreCase(MimeConstants.CREATION_DATE)) {
301: creation_date = quote(value.toLowerCase(), true);
302: } else if (pname
303: .equalsIgnoreCase(MimeConstants.MODIFICATION_DATE)) {
304: modification_date = quote(value.toLowerCase(), true);
305: } else if (pname.equalsIgnoreCase(MimeConstants.READ_DATE)) {
306: read_date = quote(value.toLowerCase(), true);
307: } else if (pname.equalsIgnoreCase(MimeConstants.SIZE)) {
308: size = quote(value.toLowerCase(), true);
309: }
310: }
311:
312: // no sanity checks
313: if (charset != null)
314: _fs += _SC + MimeConstants.CHARSET + _EQ + charset;
315:
316: if (creation_date != null)
317: _fs += _SC + MimeConstants.CREATION_DATE + _EQ
318: + creation_date;
319:
320: if (filename != null)
321: _fs += _SC + MimeConstants.FILENAME + _EQ + filename;
322:
323: if (modification_date != null)
324: _fs += _SC + MimeConstants.MODIFICATION_DATE + _EQ
325: + modification_date;
326:
327: if (padding != null)
328: _fs += _SC + MimeConstants.PADDING + _EQ + padding;
329:
330: if (read_date != null)
331: _fs += _SC + MimeConstants.READ_DATE + _EQ + read_date;
332:
333: if (size != null)
334: _fs += _SC + MimeConstants.SIZE + _EQ + size;
335:
336: if (type != null)
337: _fs += _SC + MimeConstants.TYPE + _EQ + type;
338:
339: return _fs;
340: }
341:
342: private Vector makeParameterVector(String input) {
343: Vector v = new Vector();
344:
345: StringTokenizer nzr = new StringTokenizer(input, _SC);
346: while (nzr.hasMoreTokens()) {
347: v.add(nzr.nextToken());
348: }
349:
350: return v;
351: }
352:
353: private String _rfc2184decode(String input) throws Exception {
354: StringTokenizer nzr = new StringTokenizer(input, _SQ);
355:
356: if (nzr.countTokens() != 3) {
357: // log
358: throw new XWSSecurityException(
359: "Malformed RFC2184 encoded parameter");
360: }
361:
362: String charset = nzr.nextToken();
363: String language = nzr.nextToken();
364: //String encoded = nzr.nextToken();
365:
366: for (int i = 0; i < input.length(); i++) {
367: if (input.charAt(i) == _PC.charAt(0)) {
368: input = input.substring(0, i)
369: + _decodeHexadecimal(input.substring(i + 1,
370: i + 3), charset, language)
371: + input.substring(i + 3);
372: }
373: }
374:
375: return input;
376: }
377:
378: private String _decodeHexadecimal(String input, String charset,
379: String language) throws Exception {
380: // ignoring language
381: byte b = Byte.decode("0x" + input.toUpperCase()).byteValue();
382: return new String(new byte[] { b }, MimeUtility
383: .javaCharset(charset));
384: }
385:
386: private String rfc2184decode(String input) throws Exception {
387: int index = -1;
388: String pname = "";
389: String value = "";
390: String decoded = "";
391:
392: Vector v = makeParameterVector(input);
393: for (int i = 0; i < v.size(); i++) {
394: String token = (String) v.elementAt(i);
395:
396: int idx = token.indexOf(_EQ);
397: String pn = token.substring(0, idx).trim();
398: String pv = token.substring(idx + 1).trim();
399:
400: if (pn.endsWith(_AX)) {
401: // is language encoded, strip _AX
402: pn = pn.substring(0, pn.length() - 1);
403: pv = _rfc2184decode(pv);
404:
405: token = pn + _EQ + pv;
406:
407: v.setElementAt(token, i);
408:
409: i--;
410:
411: } else {
412: int ix = pn.indexOf(_AX);
413:
414: if (ix == -1) {
415: // flush out the previous param
416: if (!pname.equals(""))
417: decoded += _SC + pname + _EQ + pv;
418:
419: // write the current param
420: decoded += _SC + pn + _EQ + pv;
421:
422: // reset state
423: pname = "";
424: value = "";
425: index = -1;
426: continue;
427: }
428:
429: // parameter continuation
430: String pn_i = pn.substring(0, ix).trim();
431: int curr = new Integer(pn.substring(ix + 1).trim())
432: .intValue();
433:
434: if (pn_i.equalsIgnoreCase(pname)) {
435: if (curr != index + 1) {
436: // log
437: throw new XWSSecurityException(
438: "Malformed RFC2184 encoded parameter");
439: }
440:
441: value += concatenate2184decoded(value, pv);
442: index++;
443:
444: } else {
445: if (curr == 0) {
446: // flush out previous param
447: if (!pname.equals(""))
448: decoded += _SC + pname + _EQ + value;
449:
450: // store state
451: pname = pn_i;
452: value = pv;
453: index++;
454: } else {
455: // log
456: throw new XWSSecurityException(
457: "Malformed RFC2184 encoded parameter");
458: }
459: }
460: }
461:
462: }
463:
464: // flush out an unwritten param
465: if (!pname.equals(""))
466: decoded += _SC + pname + _EQ + value;
467:
468: return decoded;
469: }
470:
471: private String concatenate2184decoded(String v0, String v1)
472: throws XWSSecurityException {
473: boolean v0Quoted = (v0.charAt(0) == _QT.charAt(0) && v0
474: .charAt(v0.length() - 1) == _QT.charAt(0));
475: boolean v1Quoted = (v1.charAt(0) == _QT.charAt(0) && v1
476: .charAt(0) == _QT.charAt(0));
477:
478: if (v0Quoted != v1Quoted) {
479: // log
480: throw new XWSSecurityException(
481: "Malformed RFC2184 encoded parameter");
482: }
483:
484: String value = null;
485:
486: if (v0Quoted) {
487: value = v0.substring(0, v0.length() - 1) + v1.substring(1);
488: } else
489: value = v0 + v1;
490:
491: return value;
492: }
493:
494: private String quote(String input, boolean force) {
495: if (input.charAt(0) == _QT.charAt(0)
496: || input.charAt(input.length() - 1) == _QT.charAt(0))
497: input = _QT
498: + unquoteInner(input.substring(1,
499: input.length() - 1)) + _QT;
500: else if (force)
501: input = _QT + quoteInner(unfoldWS(input).trim()) + _QT;
502: else
503: input = unfoldWS(input).trim();
504:
505: return input;
506: }
507:
508: private String unquoteInner(String input) {
509: StringBuffer sb = new StringBuffer();
510:
511: for (int i = 0; i < input.length(); i++) {
512: char c = input.charAt(i);
513:
514: if (c == _BS.charAt(0)) {
515: if (i == input.length() - 1) {
516: sb.append(c);
517: sb.append(c);
518: break;
519: }
520:
521: i++;
522: char d = input.charAt(i);
523: if (d == _QT.charAt(0) || d == _BS.charAt(0)) {
524: sb.append(c);
525: sb.append(d);
526: continue;
527: } else {
528: sb.append(d);
529: continue;
530: }
531: }
532:
533: if (c == _QT.charAt(0)) {
534: sb.append(_BS.charAt(0));
535: sb.append(c);
536: }
537:
538: sb.append(c);
539: }
540:
541: return sb.toString();
542: }
543:
544: private String quoteInner(String input) {
545: StringBuffer sb = new StringBuffer();
546:
547: for (int i = 0; i < input.length(); i++) {
548: char c = input.charAt(i);
549:
550: if (c == _BS.charAt(0) || c == _QT.charAt(0))
551: sb.append(_BS.charAt(0));
552:
553: sb.append(c);
554: }
555:
556: return sb.toString();
557: }
558:
559: // rfc2822 mime header delimiters
560: private static final String _WS = " ";
561: private static final String _SC = ";";
562: private static final String _BS = "\\";
563: private static final String _FS = "/";
564: private static final String _EQ = "=";
565: private static final String _CL = ":";
566: private static final String _OC = "(";
567: private static final String _CC = ")";
568: private static final String _QT = "\"";
569: private static final String _SQ = "'";
570: private static final String _HT = "\t";
571: private static final String _AX = "*";
572: private static final String _PC = "%";
573:
574: private static final String _CRLF = "\r\n";
575: }
|