KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > RedirectionModule


1 /*
2  * @(#)RedirectionModule.java 0.3-2 18/06/1999
3  *
4  * This file is part of the HTTPClient package
5  * Copyright (C) 1996-1999 Ronald Tschalär
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free
19  * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20  * MA 02111-1307, USA
21  *
22  * For questions, suggestions, bug-reports, enhancement-requests etc.
23  * I may be contacted at:
24  *
25  * ronald@innovation.ch
26  *
27  */

28
29 package HTTPClient;
30
31 import java.net.InetAddress JavaDoc;
32 import java.net.ProtocolException JavaDoc;
33 import java.net.UnknownHostException JavaDoc;
34 import java.io.IOException JavaDoc;
35 import java.util.Hashtable JavaDoc;
36
37
38 /**
39  * This module handles the redirection status codes 301, 302, 303, 305, 306
40  * and 307.
41  *
42  * @version 0.3-2 18/06/1999
43  * @author Ronald Tschalär
44  */

45
46 class RedirectionModule implements HTTPClientModule, GlobalConstants
47 {
48     /** a list of permanent redirections (301) */
49     private static Hashtable JavaDoc perm_redir_cntxt_list = new Hashtable JavaDoc();
50
51     /** the level of redirection */
52     private int level;
53
54     /** the url used in the last redirection */
55     private URI lastURI;
56
57
58     // Constructors
59

60     /**
61      * Start with level 0.
62      */

63     RedirectionModule()
64     {
65     level = 0;
66     lastURI = null;
67     }
68
69
70     // Methods
71

72     /**
73      * Invoked by the HTTPClient.
74      */

75     public int requestHandler(Request req, Response[] resp)
76     {
77     HTTPConnection con = req.getConnection();
78     URI new_loc,
79         cur_loc;
80         
81     try
82     {
83         cur_loc = new URI(con.getProtocol(), con.getHost(),
84                   con.getPort(), req.getRequestURI());
85     }
86     catch (ParseException pe)
87     {
88         throw new Error JavaDoc("HTTPClient Internal Error: unexpected exception '"
89                 + pe + "'");
90     }
91
92
93     // handle permanent redirections
94

95     Hashtable JavaDoc perm_redir_list = Util.getList(perm_redir_cntxt_list,
96                         req.getConnection().getContext());
97     if ((new_loc = (URI) perm_redir_list.get(cur_loc)) != null)
98     {
99         /* copy query if present in old url but not in new url. This
100          * isn't strictly conforming, but some scripts fail to properly
101          * propagate the query string to the Location header.
102          *
103          * Unfortunately it looks like we're fucked either way: some
104          * scripts fail if you don't propagate the query string, some
105          * fail if you do... God, don't you just love it when people
106          * can't read a spec? Anway, since we can't get it right for
107          * all scripts we opt to follow the spec.
108         String nres = new_loc.getPath(),
109            oquery = Util.getQuery(req.getRequestURI()),
110            nquery = Util.getQuery(nres);
111         if (nquery == null && oquery != null)
112         nres += "?" + oquery;
113          */

114         String JavaDoc nres = new_loc.getPath();
115         req.setRequestURI(nres);
116
117         try
118         { lastURI = new URI(new_loc, nres); }
119         catch (ParseException pe)
120         { }
121
122         if (DebugMods)
123         System.err.println("RdirM: matched request in permanent " +
124                    "redirection list - redoing request to " +
125                    lastURI);
126
127         if (!sameServer(con, new_loc))
128         {
129         try
130             { con = new HTTPConnection(new_loc.toURL()); }
131         catch (Exception JavaDoc e)
132         {
133             throw new Error JavaDoc("HTTPClient Internal Error: unexpected " +
134                     "exception '" + e + "'");
135         }
136
137         con.setContext(req.getConnection().getContext());
138         req.setConnection(con);
139         return REQ_NEWCON_RST;
140         }
141         else
142         {
143         return REQ_RESTART;
144         }
145     }
146
147     return REQ_CONTINUE;
148     }
149
150
151     /**
152      * Invoked by the HTTPClient.
153      */

154     public void responsePhase1Handler(Response resp, RoRequest req)
155         throws IOException JavaDoc
156     {
157     int sts = resp.getStatusCode();
158     if (sts < 301 || sts > 307 || sts == 304)
159     {
160         if (lastURI != null) // it's been redirected
161
resp.setEffectiveURI(lastURI);
162     }
163     }
164
165
166     /**
167      * Invoked by the HTTPClient.
168      */

169     public int responsePhase2Handler(Response resp, Request req)
170         throws IOException JavaDoc
171     {
172     /* handle various response status codes until satisfied */
173
174     int sts = resp.getStatusCode();
175     switch(sts)
176     {
177         case 302: // General (temporary) Redirection (handle like 303)
178

179         if (DebugMods)
180             System.err.println("RdirM: Received status: " + sts +
181                        " " + resp.getReasonLine() +
182                        " - treating as 303");
183
184         sts = 303;
185
186         case 301: // Moved Permanently
187
case 303: // See Other (use GET)
188
case 307: // Moved Temporarily (we mean it!)
189

190         if (DebugMods)
191             System.err.println("RdirM: Handling status: " + sts +
192                        " " + resp.getReasonLine());
193
194         // the spec says automatic redirection may only be done if
195
// the second request is a HEAD or GET.
196
if (!req.getMethod().equals("GET") &&
197             !req.getMethod().equals("HEAD") &&
198             sts != 303)
199         {
200             if (DebugMods)
201             System.err.println("RdirM: not redirected because " +
202                        "method is neither HEAD nor GET");
203
204             if (sts == 301 && resp.getHeader("Location") != null)
205             update_perm_redir_list(req,
206                     resLocHdr(resp.getHeader("Location"), req));
207
208             resp.setEffectiveURI(lastURI);
209             return RSP_CONTINUE;
210         }
211
212         case 305: // Use Proxy
213
case 306: // Switch Proxy
214

215         if (DebugMods)
216             if (sts == 305 || sts == 306)
217             System.err.println("RdirM: Handling status: " + sts +
218                        " " + resp.getReasonLine());
219
220         // Don't accept 305 from a proxy
221
if (sts == 305 && req.getConnection().getProxyHost() != null)
222         {
223             if (DebugMods)
224             System.err.println("RdirM: 305 ignored because " +
225                        "a proxy is already in use");
226
227             resp.setEffectiveURI(lastURI);
228             return RSP_CONTINUE;
229         }
230
231
232         /* the level is a primitive way of preventing infinite
233          * redirections. RFC-2068 set the max to 5, but the latest
234          * http draft has loosened this. Since some sites (notably
235          * M$) need more levels, this is now set to the (arbitrary)
236          * value of 15 (god only knows why they need to do even 5
237          * redirections...).
238          */

239         if (level == 15 || resp.getHeader("Location") == null)
240         {
241             if (DebugMods)
242             {
243             if (level == 15)
244                 System.err.println("RdirM: not redirected because "+
245                        "of too many levels of redirection");
246             else
247                 System.err.println("RdirM: not redirected because "+
248                        "no Location header was present");
249             }
250
251             resp.setEffectiveURI(lastURI);
252             return RSP_CONTINUE;
253         }
254         level++;
255
256         URI loc = resLocHdr(resp.getHeader("Location"), req);
257         
258         if (req.getStream() != null && (sts == 306 || sts == 305))
259             return RSP_CONTINUE;
260
261         HTTPConnection mvd;
262         boolean new_con = false;
263         String JavaDoc nres;
264
265         if (sts == 305)
266         {
267             mvd = new HTTPConnection(req.getConnection().getProtocol(),
268                          req.getConnection().getHost(),
269                          req.getConnection().getPort());
270             mvd.setCurrentProxy(loc.getHost(), loc.getPort());
271             mvd.setContext(req.getConnection().getContext());
272             new_con = true;
273
274             nres = req.getRequestURI();
275
276             /* There was some discussion about this, and especially
277              * Foteos Macrides (Lynx) said a 305 should also imply
278              * a change to GET (for security reasons) - see the thread
279              * starting at
280              * http://www.ics.uci.edu/pub/ietf/http/hypermail/1997q4/0351.html
281              * However, this is not in the latest draft, but since I
282              * agree with Foteos we do it anyway...
283              */

284             req.setMethod("GET");
285             req.setData(null);
286             req.setStream(null);
287         }
288         else if (sts == 306)
289         {
290             // We'll have to wait for Josh to create a new spec here.
291
return RSP_CONTINUE;
292         }
293         else
294         {
295             if (sameServer(req.getConnection(), loc))
296             {
297             mvd = req.getConnection();
298             nres = loc.getPath();
299             }
300             else
301             {
302             try
303             {
304                 mvd = new HTTPConnection(loc.toURL());
305                 nres = loc.getPath();
306             }
307             catch (Exception JavaDoc e)
308             {
309                 if (req.getConnection().getProxyHost() == null ||
310                 !loc.getScheme().equalsIgnoreCase("ftp"))
311                 return RSP_CONTINUE;
312
313                 // We're using a proxy and the protocol is ftp -
314
// maybe the proxy will also proxy ftp...
315
mvd = new HTTPConnection("http",
316                         req.getConnection().getProxyHost(),
317                         req.getConnection().getProxyPort());
318                 mvd.setCurrentProxy(null, 0);
319                 nres = loc.toExternalForm();
320             }
321
322             mvd.setContext(req.getConnection().getContext());
323             new_con = true;
324             }
325
326             /* copy query if present in old url but not in new url.
327              * This isn't strictly conforming, but some scripts fail
328              * to propagate the query properly to the Location
329              * header.
330              *
331              * See comment on line 99.
332             String oquery = Util.getQuery(req.getRequestURI()),
333                nquery = Util.getQuery(nres);
334             if (nquery == null && oquery != null)
335             nres += "?" + oquery;
336              */

337
338             if (sts == 303 && !req.getMethod().equals("HEAD"))
339             {
340             // 303 means "use GET"
341

342             req.setMethod("GET");
343             req.setData(null);
344             req.setStream(null);
345             }
346             else if (sts == 301)
347             {
348             // update permanent redirection list
349
try
350             {
351                 update_perm_redir_list(req, new URI(loc, nres));
352             }
353             catch (ParseException pe)
354                 { /* ??? */ }
355
356             }
357
358             // Adjust Referer, if present
359
NVPair[] hdrs = req.getHeaders();
360             for (int idx=0; idx<hdrs.length; idx++)
361             if (hdrs[idx].getName().equalsIgnoreCase("Referer"))
362             {
363                 HTTPConnection con = req.getConnection();
364                 hdrs[idx] =
365                 new NVPair("Referer", con+req.getRequestURI());
366                 break;
367             }
368         }
369
370         req.setConnection(mvd);
371         req.setRequestURI(nres);
372
373         try { resp.getInputStream().close(); }
374         catch (IOException JavaDoc ioe) { }
375
376         if (sts != 305 && sts != 306)
377         {
378             try
379             { lastURI = new URI(loc, nres); }
380             catch (ParseException pe)
381             { /* ??? */ }
382
383             if (DebugMods)
384             System.err.println("RdirM: request redirected to " +
385                         lastURI + " using method " +
386                         req.getMethod());
387         }
388         else
389         {
390             if (DebugMods)
391             System.err.println("RdirM: resending request using " +
392                         "proxy " + mvd.getProxyHost() +
393                         ":" + mvd.getProxyPort());
394         }
395
396         if (new_con)
397             return RSP_NEWCON_REQ;
398         else
399             return RSP_REQUEST;
400
401         default:
402
403         return RSP_CONTINUE;
404     }
405     }
406
407
408     /**
409      * Invoked by the HTTPClient.
410      */

411     public void responsePhase3Handler(Response resp, RoRequest req)
412     {
413     }
414
415
416     /**
417      * Invoked by the HTTPClient.
418      */

419     public void trailerHandler(Response resp, RoRequest req)
420     {
421     }
422
423
424     /**
425      * Update the permanent redirection list.
426      *
427      * @param the original request
428      * @param the new location
429      */

430     private static void update_perm_redir_list(RoRequest req, URI new_loc)
431     {
432     HTTPConnection con = req.getConnection();
433     URI cur_loc = null;
434     try
435     {
436         cur_loc = new URI(con.getProtocol(),
437                   con.getHost(),
438                   con.getPort(),
439                   req.getRequestURI());
440     }
441     catch (ParseException pe)
442         { }
443
444     if (!cur_loc.equals(new_loc))
445     {
446         Hashtable JavaDoc perm_redir_list =
447             Util.getList(perm_redir_cntxt_list, con.getContext());
448         perm_redir_list.put(cur_loc, new_loc);
449     }
450     }
451
452
453     /**
454      * The Location header field must be an absolute URI, but too many broken
455      * servers use relative URIs. So, try as an absolute URI, and if that
456      * fails try as a relative URI.
457      *
458      * @param loc the Location header field
459      * @param req the Request to resolve relative URI's relative to
460      * @return an absolute URI corresponding to the Location header field
461      * @ throws ProtocolException if the Location header field is completely
462      * unparseable
463      */

464     private URI resLocHdr(String JavaDoc loc, RoRequest req) throws ProtocolException JavaDoc
465     {
466     try
467         { return new URI(loc); }
468     catch (ParseException pe)
469     {
470         // it might be a relative URL (i.e. another broken server)
471
try
472         {
473         URI base = new URI(req.getConnection().getProtocol(),
474                    req.getConnection().getHost(),
475                    req.getConnection().getPort(),
476                    req.getRequestURI());
477         return new URI(base, loc);
478         }
479         catch (ParseException pe2)
480         {
481         throw new ProtocolException JavaDoc("Malformed URL in Location " +
482                         "header: " + loc);
483         }
484     }
485     }
486
487
488     /**
489      * Tries to determine as best as possible if <var>url</var> refers
490      * to the same server as <var>con</var> is talking with.
491      *
492      * @param con the HTTPConnection
493      * @param url the http URL
494      * @return true if the url refers to the same server as the connection,
495      * false otherwise.
496      */

497     private boolean sameServer(HTTPConnection con, URI url)
498     {
499     if (!url.getScheme().equalsIgnoreCase(con.getProtocol()))
500         return false;
501
502     /* we can't do this, because otherwise a server can't redirect to
503      * a new host name (that resolves to the same ip-address as the
504      * old host name).
505     try
506     {
507         compAddr: if (!url.getHost().equalsIgnoreCase(con.getHost()))
508         {
509         InetAddress[] list1 = InetAddress.getAllByName(url.getHost());
510         InetAddress[] list2 = InetAddress.getAllByName(con.getHost());
511         for (int idx1=0; idx1<list1.length; idx1++)
512             for (int idx2=0; idx2<list2.length; idx2++)
513             if (list1[idx1].equals(list2[idx2]))
514                 break compAddr;
515         return false;
516         }
517     }
518     catch (UnknownHostException uhe)
519         { return false; }
520      */

521     if (!url.getHost().equalsIgnoreCase(con.getHost()))
522         return false;
523
524     if (url.getPort() != con.getPort())
525         return false;
526
527     return true;
528     }
529 }
530
531
Popular Tags