001: /*
002: * @(#)ContentMD5Module.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.IOException;
032: import java.net.ProtocolException;
033:
034: /**
035: * This module handles the Content-MD5 response header. If this header was
036: * sent with a response and the entity isn't encoded using an unknown
037: * transport encoding then an MD5InputStream is wrapped around the response
038: * input stream. The MD5InputStream keeps a running digest and checks this
039: * against the expected digest from the Content-MD5 header the stream is
040: * closed. An IOException is thrown at that point if the digests don't match.
041: *
042: * @version 0.3-2 18/06/1999
043: * @author Ronald Tschalär
044: */
045:
046: class ContentMD5Module implements HTTPClientModule, GlobalConstants {
047: // Constructors
048:
049: ContentMD5Module() {
050: }
051:
052: // Methods
053:
054: /**
055: * Invoked by the HTTPClient.
056: */
057: public int requestHandler(Request req, Response[] resp) {
058: return REQ_CONTINUE;
059: }
060:
061: /**
062: * Invoked by the HTTPClient.
063: */
064: public void responsePhase1Handler(Response resp, RoRequest req) {
065: }
066:
067: /**
068: * Invoked by the HTTPClient.
069: */
070: public int responsePhase2Handler(Response resp, Request req) {
071: return RSP_CONTINUE;
072: }
073:
074: /**
075: * Invoked by the HTTPClient.
076: */
077: public void responsePhase3Handler(Response resp, RoRequest req)
078: throws IOException, ModuleException {
079: if (req.getMethod().equals("HEAD"))
080: return;
081:
082: String md5_digest = resp.getHeader("Content-MD5");
083: String trailer = resp.getHeader("Trailer");
084: boolean md5_tok = false;
085: try {
086: if (trailer != null)
087: md5_tok = Util.hasToken(trailer, "Content-MD5");
088: } catch (ParseException pe) {
089: throw new ModuleException(pe.toString());
090: }
091:
092: if ((md5_digest == null && !md5_tok)
093: || resp.getHeader("Transfer-Encoding") != null)
094: return;
095:
096: if (DebugMods) {
097: if (md5_digest != null)
098: System.err.println("CMD5M: Received digest: "
099: + md5_digest + " - pushing md5-check-stream");
100: else
101: System.err
102: .println("CMD5M: Expecting digest in trailer "
103: + " - pushing md5-check-stream");
104: }
105:
106: resp.inp_stream = new MD5InputStream(resp.inp_stream,
107: new VerifyMD5(resp));
108: }
109:
110: /**
111: * Invoked by the HTTPClient.
112: */
113: public void trailerHandler(Response resp, RoRequest req) {
114: }
115: }
116:
117: class VerifyMD5 implements HashVerifier, GlobalConstants {
118: RoResponse resp;
119:
120: public VerifyMD5(RoResponse resp) {
121: this .resp = resp;
122: }
123:
124: public void verifyHash(byte[] hash, long len) throws IOException {
125: String hdr;
126: try {
127: if ((hdr = resp.getHeader("Content-MD5")) == null)
128: hdr = resp.getTrailer("Content-MD5");
129: } catch (IOException ioe) {
130: return;
131: } // shouldn't happen
132:
133: if (hdr == null)
134: return;
135:
136: hdr = hdr.trim();
137: byte[] ContMD5 = new byte[hdr.length()];
138: hdr.getBytes(0, ContMD5.length, ContMD5, 0);
139: ContMD5 = Codecs.base64Decode(ContMD5);
140:
141: for (int idx = 0; idx < hash.length; idx++) {
142: if (hash[idx] != ContMD5[idx])
143: throw new IOException("MD5-Digest mismatch: expected "
144: + hex(ContMD5) + " but calculated " + hex(hash));
145: }
146:
147: if (DebugMods)
148: System.err.println("CMD5M: hash successfully verified");
149: }
150:
151: /**
152: * Produce a string of the form "A5:22:F1:0B:53"
153: */
154: private static String hex(byte[] buf) {
155: StringBuffer str = new StringBuffer(buf.length * 3);
156: for (int idx = 0; idx < buf.length; idx++) {
157: str.append(Character.forDigit((buf[idx] >>> 4) & 15, 16));
158: str.append(Character.forDigit(buf[idx] & 15, 16));
159: str.append(':');
160: }
161: str.setLength(str.length() - 1);
162:
163: return str.toString();
164: }
165: }
|