View | Details | Raw Unified | Return to bug 51497
Collapse All | Expand All

(-)java/org/apache/catalina/valves/AccessLogValve.java (-11 / +56 lines)
Lines 53-58 Link Here
53
import org.apache.juli.logging.LogFactory;
53
import org.apache.juli.logging.LogFactory;
54
import org.apache.tomcat.util.ExceptionUtils;
54
import org.apache.tomcat.util.ExceptionUtils;
55
import org.apache.tomcat.util.buf.B2CConverter;
55
import org.apache.tomcat.util.buf.B2CConverter;
56
import org.apache.tomcat.util.net.IPv6Utils;
56
57
57
58
58
/**
59
/**
Lines 222-227 Link Here
222
223
223
224
224
    /**
225
    /**
226
     * Use IPv6 canonical representation format as defined by RFC 5952.
227
     */
228
    protected boolean canonical = true;
229
230
231
    /**
225
     * The PrintWriter to which we are currently logging, if any.
232
     * The PrintWriter to which we are currently logging, if any.
226
     */
233
     */
227
    protected PrintWriter writer = null;
234
    protected PrintWriter writer = null;
Lines 232-239 Link Here
232
     * "yyyy-MM-dd".
239
     * "yyyy-MM-dd".
233
     */
240
     */
234
    protected SimpleDateFormat fileDateFormatter = null;
241
    protected SimpleDateFormat fileDateFormatter = null;
242
    
235
243
236
237
    /**
244
    /**
238
     * The system timezone.
245
     * The system timezone.
239
     */
246
     */
Lines 748-753 Link Here
748
755
749
756
750
    /**
757
    /**
758
     * Are IPv6 addresses represented in canonical representation format
759
     */
760
    public boolean isCanonical() {
761
        return canonical;
762
    }
763
764
765
    /**
766
     * Set the value if IPv6 addresses should be represented in canonical representation format?
767
     *
768
     * @param canonical true if canonical.
769
     */
770
    public void setCanonical(boolean canonical) {
771
        this.canonical = canonical;
772
    }
773
774
775
    /**
751
     * Set the resolve hosts flag.
776
     * Set the resolve hosts flag.
752
     *
777
     *
753
     * @param resolveHosts The new resolve hosts value
778
     * @param resolveHosts The new resolve hosts value
Lines 1217-1227 Link Here
1217
    /**
1242
    /**
1218
     * write local IP address - %A
1243
     * write local IP address - %A
1219
     */
1244
     */
1220
    protected static class LocalAddrElement implements AccessLogElement {
1245
    protected class LocalAddrElement implements AccessLogElement {
1221
        
1246
        
1222
        private static final String LOCAL_ADDR_VALUE;
1247
        private String localAddrValue;
1223
1248
1224
        static {
1249
        private void lazyInit() {
1225
            String init;
1250
            String init;
1226
            try {
1251
            try {
1227
                init = InetAddress.getLocalHost().getHostAddress();
1252
                init = InetAddress.getLocalHost().getHostAddress();
Lines 1229-1241 Link Here
1229
                ExceptionUtils.handleThrowable(e);
1254
                ExceptionUtils.handleThrowable(e);
1230
                init = "127.0.0.1";
1255
                init = "127.0.0.1";
1231
            }
1256
            }
1232
            LOCAL_ADDR_VALUE = init;
1257
            if (canonical) {
1258
                localAddrValue = IPv6Utils.canonize(init);
1259
            } else {
1260
                localAddrValue = init;
1261
            }
1233
        }
1262
        }
1234
        
1263
        
1235
        @Override
1264
        @Override
1236
        public void addElement(StringBuilder buf, Date date, Request request,
1265
        public void addElement(StringBuilder buf, Date date, Request request,
1237
                Response response, long time) {
1266
                Response response, long time) {
1238
            buf.append(LOCAL_ADDR_VALUE);
1267
            if (localAddrValue == null) {
1268
               lazyInit();
1269
            }
1270
            buf.append(localAddrValue);
1239
        }
1271
        }
1240
    }
1272
    }
1241
    
1273
    
Lines 1246-1261 Link Here
1246
        @Override
1278
        @Override
1247
        public void addElement(StringBuilder buf, Date date, Request request,
1279
        public void addElement(StringBuilder buf, Date date, Request request,
1248
                Response response, long time) {
1280
                Response response, long time) {
1281
            String value = null;
1249
            if (requestAttributesEnabled) {
1282
            if (requestAttributesEnabled) {
1250
                Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
1283
                Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
1251
                if (addr == null) {
1284
                if (addr == null) {
1252
                    buf.append(request.getRemoteAddr());
1285
                    value = request.getRemoteAddr();
1253
                } else {
1286
                } else {
1254
                    buf.append(addr);
1287
                    value = addr.toString();
1255
                }
1288
                }
1256
            } else {
1289
            } else {
1257
                buf.append(request.getRemoteAddr());
1290
                value = request.getRemoteAddr();
1258
            }
1291
            }
1292
            
1293
            if (canonical) {
1294
                value = IPv6Utils.canonize(value);
1295
            }
1296
            buf.append(value);
1259
        }
1297
        }
1260
    }
1298
    }
1261
    
1299
    
Lines 1279-1284 Link Here
1279
            if (value == null || value.length() == 0) {
1317
            if (value == null || value.length() == 0) {
1280
                value = "-";
1318
                value = "-";
1281
            }
1319
            }
1320
            if (canonical) {
1321
                value = IPv6Utils.canonize(value);
1322
            }
1282
            buf.append(value);
1323
            buf.append(value);
1283
        }
1324
        }
1284
    }
1325
    }
Lines 1691-1701 Link Here
1691
    /**
1732
    /**
1692
     * write local server name - %v
1733
     * write local server name - %v
1693
     */
1734
     */
1694
    protected static class LocalServerNameElement implements AccessLogElement {
1735
    protected class LocalServerNameElement implements AccessLogElement {
1695
        @Override
1736
        @Override
1696
        public void addElement(StringBuilder buf, Date date, Request request,
1737
        public void addElement(StringBuilder buf, Date date, Request request,
1697
                Response response, long time) {
1738
                Response response, long time) {
1698
            buf.append(request.getServerName());
1739
            if (canonical) {
1740
                buf.append(IPv6Utils.canonize(request.getServerName()));
1741
            } else {
1742
                buf.append(request.getServerName());
1743
            }
1699
        }
1744
        }
1700
    }
1745
    }
1701
    
1746
    
(-)java/org/apache/tomcat/util/net/IPv6Utils.java (+165 lines)
Line 0 Link Here
1
/*
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
3
 *  contributor license agreements.  See the NOTICE file distributed with
4
 *  this work for additional information regarding copyright ownership.
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
6
 *  (the "License"); you may not use this file except in compliance with
7
 *  the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *  Unless required by applicable law or agreed to in writing, software
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *  See the License for the specific language governing permissions and
15
 *  limitations under the License.
16
 */
17
package org.apache.tomcat.util.net;
18
19
import java.net.Inet6Address;
20
import java.net.InetAddress;
21
import java.net.UnknownHostException;
22
23
/**
24
 * <p>IPv6 utilities.
25
 * <p>For the moment, it only contains function to canonize IPv6 address into 
26
 * RFC 5952 form.
27
 */
28
public class IPv6Utils {
29
    
30
    private static final int MAX_NUMBER_OF_GROUPS = 8;
31
    private static final int MAX_GROUP_LENGTH = 4;
32
33
    /**
34
     * Convert IPv6 adress into RFC 5952 form. 
35
     * (e.g. 2001:db8:0:1:0:0:0:1 -> 2001:db8:0:1::1).
36
     * Function is null safe, and if IPv4 address or host name is passed to the 
37
     * function it is returned wihout any processing.
38
     * 
39
     * @param ipv6Address String representing valid IPv6 address.
40
     * @return String representing IPv6 in canonical form.
41
     * @throws IllegalArgumentException if IPv6 format is unacceptable. 
42
     */
43
    public static String canonize(String ipv6Address)
44
            throws IllegalArgumentException {
45
        
46
        if (ipv6Address == null) {
47
            return null;
48
        }
49
        
50
        // Definitly not an IPv6, return untouched input.
51
        if (!mayBeIPv6Address(ipv6Address)) {
52
            return ipv6Address;
53
        }
54
        
55
        StringBuilder result = new StringBuilder();
56
        char [][] groups = new char[MAX_NUMBER_OF_GROUPS][MAX_GROUP_LENGTH];
57
        int groupCounter = 0;
58
        int charInGroupCounter = 0;
59
60
        // Index of the current zeroGroup, -1 means not found.
61
        int zeroGroupIndex = -1;
62
        int zeroGroupLength = 0;
63
        
64
        // maximum length zero group, if there is more then one, then first one
65
        int maxZeroGroupIndex = -1;
66
        int maxZeroGroupLength = 0;
67
        
68
        boolean isZero = true;
69
        String expanded = null;
70
        try {
71
            InetAddress inetAddress = InetAddress.getByName(ipv6Address);
72
            if (inetAddress instanceof Inet6Address) {
73
                expanded = ((Inet6Address) inetAddress).getHostAddress();
74
            } else {
75
                // Probably IPv4 address, leave it as it is
76
                return ipv6Address;
77
            }
78
        } catch (UnknownHostException uhe) {
79
            // Malformed input
80
            throw new IllegalArgumentException(uhe);
81
        }
82
        
83
        int len = expanded.length();
84
        
85
        // Processing one char at the time
86
        for (int charCounter = 0; charCounter < len; charCounter++) {
87
            char c = expanded.charAt(charCounter);
88
            //System.out.println(c);
89
            if (c != ':') {
90
                groups[groupCounter][charInGroupCounter] = c;
91
                ++charInGroupCounter;
92
                if (c != '0') {
93
                    isZero = false;
94
                }
95
            } 
96
            if (c == ':' || charCounter == (len - 1)) {
97
                // We reached end of current group
98
                if (isZero && zeroGroupIndex == -1) {
99
                    zeroGroupIndex = groupCounter;
100
                    zeroGroupLength = 1;
101
                } else if (!isZero) {
102
                    // We reached end of zero group
103
                    if (zeroGroupLength > maxZeroGroupLength) {
104
                        maxZeroGroupLength = zeroGroupLength;
105
                        maxZeroGroupIndex = zeroGroupIndex;
106
                    }
107
                    zeroGroupLength = 0;
108
                    zeroGroupIndex = -1;
109
                } else {
110
                    ++zeroGroupLength;
111
                }
112
                ++groupCounter;
113
                charInGroupCounter = 0;
114
                isZero = true;
115
            }
116
        }
117
        
118
        // Output results
119
        for (groupCounter = 0; groupCounter < MAX_NUMBER_OF_GROUPS; groupCounter++) {
120
            if (maxZeroGroupLength <= 1 || groupCounter < maxZeroGroupIndex
121
                    || groupCounter >= maxZeroGroupIndex + maxZeroGroupLength) {
122
                for (int j = 0; j < MAX_GROUP_LENGTH; j++) {
123
                    if (groups[groupCounter][j] != 0) {
124
                        result.append(groups[groupCounter][j]);
125
                    }
126
                }
127
                if (groupCounter < (MAX_NUMBER_OF_GROUPS - 1)
128
                        && (groupCounter != maxZeroGroupIndex - 1 
129
                                || maxZeroGroupLength <= 1)) {
130
                    result.append(':');
131
                }
132
            } else if (groupCounter == maxZeroGroupIndex) {
133
                result.append("::");
134
            }
135
        }
136
        
137
        return result.toString();
138
    }
139
    
140
    /**
141
     * Heuristic check if string might be an IPv6 address.
142
     * 
143
     * @param address Any string or null
144
     * @return true, if string contains only hex digits and at least two colons. 
145
     */
146
    private static boolean mayBeIPv6Address(String input) {
147
        if (input == null) {
148
            return false;
149
        }
150
        boolean result = true;
151
        int colonsCounter = 0;
152
        int length = input.length();
153
        for (int i = 0; i < length; i++) {
154
            char c = input.charAt(i);
155
            if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') 
156
                    || (c >= 'A' && c <= 'F') || c == ':')) {
157
                result = false;
158
            } else if (c == ':') {
159
                colonsCounter++;
160
            }
161
        }
162
        return result;
163
        
164
    }
165
}
(-)test/org/apache/tomcat/util/net/IPv6UtilsTest.java (+126 lines)
Line 0 Link Here
1
/*
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
3
 *  contributor license agreements.  See the NOTICE file distributed with
4
 *  this work for additional information regarding copyright ownership.
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
6
 *  (the "License"); you may not use this file except in compliance with
7
 *  the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *  Unless required by applicable law or agreed to in writing, software
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *  See the License for the specific language governing permissions and
15
 *  limitations under the License.
16
 */
17
18
package org.apache.tomcat.util.net;
19
20
import junit.framework.TestCase;
21
22
/**
23
 * Mostly examples from RFC 5952
24
 */
25
public class IPv6UtilsTest extends TestCase {
26
27
    public void testCanonize() {
28
        assertTrue(IPv6Utils.canonize(null) == null);
29
        assertTrue(IPv6Utils.canonize("").equals(""));
30
31
        // IPv4-safe
32
        assertTrue(IPv6Utils.canonize("123.123.123.123").equals(
33
                "123.123.123.123"));
34
        assertTrue(IPv6Utils.canonize("123.1.2.23").equals("123.1.2.23"));
35
36
        // Introductory RFC 5952 examples
37
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:1:0:0:1").equals(
38
                "2001:db8::1:0:0:1"));
39
        assertTrue(IPv6Utils.canonize("2001:0db8:0:0:1:0:0:1").equals(
40
                "2001:db8::1:0:0:1"));
41
        assertTrue(IPv6Utils.canonize("2001:db8::1:0:0:1").equals(
42
                "2001:db8::1:0:0:1"));
43
        assertTrue(IPv6Utils.canonize("2001:db8::0:1:0:0:1").equals(
44
                "2001:db8::1:0:0:1"));
45
        assertTrue(IPv6Utils.canonize("2001:0db8::1:0:0:1").equals(
46
                "2001:db8::1:0:0:1"));
47
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:1::1").equals(
48
                "2001:db8::1:0:0:1"));
49
        assertTrue(IPv6Utils.canonize("2001:db8:0000:0:1::1").equals(
50
                "2001:db8::1:0:0:1"));
51
        assertTrue(IPv6Utils.canonize("2001:DB8:0:0:1::1").equals(
52
                "2001:db8::1:0:0:1"));
53
54
        // Strip leading zeros (2.1)
55
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001")
56
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"));
57
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:001")
58
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"));
59
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:01")
60
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"));
61
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1")
62
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"));
63
64
        // Zero compression (2.2)
65
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd::1")
66
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:0:1"));
67
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:0:1")
68
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:0:1"));
69
70
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:0::1")
71
                .equals("2001:db8::1"));
72
        assertTrue(IPv6Utils.canonize("2001:db8:0:0::1").equals("2001:db8::1"));
73
        assertTrue(IPv6Utils.canonize("2001:db8:0::1").equals("2001:db8::1"));
74
        assertTrue(IPv6Utils.canonize("2001:db8::1").equals("2001:db8::1"));
75
76
        assertTrue(IPv6Utils.canonize("2001:db8::aaaa:0:0:1").equals(
77
                "2001:db8::aaaa:0:0:1"));
78
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:aaaa::1").equals(
79
                "2001:db8::aaaa:0:0:1"));
80
81
        // Uppercase or lowercase (2.3)
82
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa")
83
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"));
84
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA")
85
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"));
86
        assertTrue(IPv6Utils.canonize("2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa")
87
                .equals("2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"));
88
89
        // Some more zero compression for localhost addresses
90
        assertTrue(IPv6Utils.canonize("0:0:0:0:0:0:0:1").equals("::1"));
91
        assertTrue(IPv6Utils.canonize("0000:0:0:0:0:0:0:0001").equals("::1"));
92
        assertTrue(IPv6Utils.canonize("00:00:0:0:00:00:0:01").equals("::1"));
93
        assertTrue(IPv6Utils.canonize("::0001").equals("::1"));
94
        assertTrue(IPv6Utils.canonize("::1").equals("::1"));
95
96
        // Leading zeros (4.1)
97
        assertTrue(IPv6Utils.canonize("2001:0db8::0001").equals("2001:db8::1"));
98
99
        // Shorten as much as possible (4.2.1)
100
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:0:0:2:1").equals(
101
                "2001:db8::2:1"));
102
103
        // Handling One 16-Bit 0 Field (4.2.2)
104
        assertTrue(IPv6Utils.canonize("2001:db8:0:1:1:1:1:1").equals(
105
                "2001:db8:0:1:1:1:1:1"));
106
        assertTrue(IPv6Utils.canonize("2001:db8::1:1:1:1:1").equals(
107
                "2001:db8:0:1:1:1:1:1"));
108
109
        // Choice in Placement of "::" (4.2.3)
110
        assertTrue(IPv6Utils.canonize("2001:0:0:1:0:0:0:1").equals(
111
                "2001:0:0:1::1"));
112
        assertTrue(IPv6Utils.canonize("2001:db8:0:0:1:0:0:1").equals(
113
                "2001:db8::1:0:0:1"));
114
115
        // IPv4 inside IPv6
116
        assertTrue(IPv6Utils.canonize("::ffff:192.0.2.1").equals(
117
                "::ffff:192.0.2.1"));
118
119
        // Hostname safety
120
        assertTrue(IPv6Utils.canonize("www.apache.org").equals(
121
                "www.apache.org"));        
122
        assertTrue(IPv6Utils.canonize("ipv6.google.com").equals(
123
                "ipv6.google.com"));        
124
        
125
    }
126
}
(-)webapps/docs/config/valve.xml (+10 lines)
Lines 193-198 Link Here
193
        </p>
193
        </p>
194
      </attribute>
194
      </attribute>
195
195
196
      <attribute name="canonical" required="false">
197
        <p>Flag to determine if IPv6 addresses should be represented in canonical 
198
           representation format as defined by RFC 5952. If set to <code>true</code>, 
199
           then IPv6 addresses will be written in canonical format (e.g. 
200
           <code>2001:db8::1:0:0:1</code>, <code>::1</code>), otherwise it will be 
201
           represented in full form (e.g. <code>2001:db8:0:0:1:0:0:1</code>, 
202
           <code>0:0:0:0:0:0:0:1</code>). Default value: <code>true</code>
203
        </p>
204
      </attribute>
205
196
    </attributes>
206
    </attributes>
197
207
198
    <p>Values for the <code>pattern</code> attribute are made up of literal
208
    <p>Values for the <code>pattern</code> attribute are made up of literal

Return to bug 51497