Ignore:
Timestamp:
2022-08-23T22:33:20+02:00 (2 years ago)
Author:
taylor.smock
Message:

Fix #22250: Abort on XML error elements

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/test/unit/org/openstreetmap/josm/io/OsmReaderTest.java

    r18037 r18541  
    55import static org.hamcrest.CoreMatchers.is;
    66import static org.hamcrest.MatcherAssert.assertThat;
     7import static org.junit.jupiter.api.Assertions.assertAll;
     8import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    79import static org.junit.jupiter.api.Assertions.assertEquals;
    810import static org.junit.jupiter.api.Assertions.assertFalse;
    911import static org.junit.jupiter.api.Assertions.assertNull;
     12import static org.junit.jupiter.api.Assertions.assertThrows;
    1013import static org.junit.jupiter.api.Assertions.assertTrue;
    1114import static org.junit.jupiter.api.Assertions.fail;
     
    1720import java.nio.file.Paths;
    1821import java.util.Arrays;
    19 
     22import java.util.stream.Stream;
     23
     24import org.junit.jupiter.params.ParameterizedTest;
     25import org.junit.jupiter.params.provider.Arguments;
     26import org.junit.jupiter.params.provider.MethodSource;
     27import org.junit.jupiter.params.provider.ValueSource;
    2028import org.openstreetmap.josm.TestUtils;
    2129import org.openstreetmap.josm.data.osm.DataSet;
     
    3442@BasicPreferences
    3543class OsmReaderTest {
    36     private static Options[][] options() {
    37         return new Options[][]{
    38                 new Options[]{},
    39                 new Options[]{Options.CONVERT_UNKNOWN_TO_TAGS},
    40                 new Options[]{Options.SAVE_ORIGINAL_ID},
    41                 new Options[]{Options.CONVERT_UNKNOWN_TO_TAGS, Options.SAVE_ORIGINAL_ID},
    42         };
     44    private static Stream<Arguments> options() {
     45        return Stream.of(Arguments.of((Object) null),
     46                Arguments.of((Object) new Options[0]),
     47                Arguments.of((Object) new Options[] {Options.CONVERT_UNKNOWN_TO_TAGS}),
     48                Arguments.of((Object) new Options[] {Options.SAVE_ORIGINAL_ID}),
     49                Arguments.of((Object) new Options[] {Options.CONVERT_UNKNOWN_TO_TAGS, Options.SAVE_ORIGINAL_ID}));
    4350    }
    4451
     
    8491    /**
    8592     * Unit test of {@link OsmReader#parseUnknown} - root case.
    86      * @throws Exception if any error occurs
    87      */
    88     @Test
    89     void testUnknownRoot() throws Exception {
    90         for (Options[] options : options()) {
    91             testUnknown("<nonosm/>", options);
    92         }
     93     * @param options The options to test
     94     * @throws Exception if any error occurs
     95     */
     96    @ParameterizedTest
     97    @MethodSource("options")
     98    void testUnknownRoot(Options... options) throws Exception {
     99        testUnknown("<nonosm/>", options);
    93100    }
    94101
    95102    /**
    96103     * Unit test of {@link OsmReader#parseUnknown} - meta case from Overpass API.
    97      * @throws Exception if any error occurs
    98      */
    99     @Test
    100     void testUnknownMeta() throws Exception {
    101         for (Options[] options : options()) {
    102             testUnknown("<osm version='0.6'><meta osm_base='2017-03-29T19:04:03Z'/></osm>", options);
    103         }
     104     * @param options The options to test
     105     * @throws Exception if any error occurs
     106     */
     107    @ParameterizedTest
     108    @MethodSource("options")
     109    void testUnknownMeta(Options... options) throws Exception {
     110        testUnknown("<osm version='0.6'><meta osm_base='2017-03-29T19:04:03Z'/></osm>", options);
    104111    }
    105112
    106113    /**
    107114     * Unit test of {@link OsmReader#parseUnknown} - note case from Overpass API.
    108      * @throws Exception if any error occurs
    109      */
    110     @Test
    111     void testUnknownNote() throws Exception {
    112         for (Options[] options : options()) {
    113             testUnknown("<osm version='0.6'><note>The data included in this document is from www.openstreetmap.org.</note></osm>", options);
    114         }
     115     * @param options The options to test
     116     * @throws Exception if any error occurs
     117     */
     118    @ParameterizedTest
     119    @MethodSource("options")
     120    void testUnknownNote(Options... options) throws Exception {
     121        testUnknown("<osm version='0.6'><note>The data included in this document is from www.openstreetmap.org.</note></osm>", options);
    115122    }
    116123
    117124    /**
    118125     * Unit test of {@link OsmReader#parseUnknown} - other cases.
    119      * @throws Exception if any error occurs
    120      */
    121     @Test
    122     void testUnknownTag() throws Exception {
    123         for (Options[] options : options()) {
    124             testUnknown("<osm version='0.6'><foo>bar</foo></osm>", options);
    125             testUnknown("<osm version='0.6'><foo><bar/></foo></osm>", options);
    126         }
     126     * @param options The options to test
     127     */
     128    @ParameterizedTest
     129    @MethodSource("options")
     130    void testUnknownTag(Options... options) {
     131        assertAll(() -> testUnknown("<osm version='0.6'><foo>bar</foo></osm>", options),
     132                () -> testUnknown("<osm version='0.6'><foo><bar/></foo></osm>", options));
    127133    }
    128134
     
    130136     * Test valid data.
    131137     * @param osm OSM data without XML prefix
     138     * @param options The options to test
    132139     * @return parsed data set
    133140     * @throws Exception if any error occurs
     
    136143        try (InputStream in = new ByteArrayInputStream(
    137144                ("<?xml version='1.0' encoding='UTF-8'?>" + osm).getBytes(StandardCharsets.UTF_8))) {
    138             return OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE, options);
     145            return assertDoesNotThrow(() -> OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE, options));
    139146        }
    140147    }
     
    143150     * Test invalid data.
    144151     * @param osm OSM data without XML prefix
    145      * @param expectedError expected error message
    146      * @throws Exception if any error occurs
    147      */
    148     private static void testInvalidData(String osm, String expectedError) throws Exception {
     152     * @return The exception
     153     */
     154    private static IllegalDataException testInvalidData(String osm) throws Exception {
    149155        try (InputStream in = new ByteArrayInputStream(
    150156                ("<?xml version='1.0' encoding='UTF-8'?>" + osm).getBytes(StandardCharsets.UTF_8))) {
    151             OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE);
    152             fail("should throw exception");
    153         } catch (IllegalDataException e) {
    154             assertEquals(expectedError, e.getMessage());
     157            return assertThrows(IllegalDataException.class, () -> OsmReader.parseDataSet(in, NullProgressMonitor.INSTANCE));
    155158        }
    156159    }
     
    162165    @Test
    163166    void testInvalidUid() throws Exception {
    164         testInvalidData("<osm version='0.6'><node id='1' uid='nan'/></osm>",
    165                 "Illegal value for attribute 'uid'. Got 'nan'. (at line 1, column 82). 82 bytes have been read");
     167        assertEquals("Illegal value for attribute 'uid'. Got 'nan'. (at line 1, column 82). 82 bytes have been read",
     168                testInvalidData("<osm version='0.6'><node id='1' uid='nan'/></osm>").getMessage());
    166169    }
    167170
     
    172175    @Test
    173176    void testMissingId() throws Exception {
    174         testInvalidData("<osm version='0.6'><node/></osm>",
    175                 "Missing required attribute 'id'. (at line 1, column 65). 64 bytes have been read");
     177        assertEquals("Missing required attribute 'id'. (at line 1, column 65). 64 bytes have been read",
     178                testInvalidData("<osm version='0.6'><node/></osm>").getMessage());
    176179    }
    177180
     
    182185    @Test
    183186    void testMissingRef() throws Exception {
    184         testInvalidData("<osm version='0.6'><way id='1' version='1'><nd/></way></osm>",
    185                 "Missing mandatory attribute 'ref' on <nd> of way 1. (at line 1, column 87). 88 bytes have been read");
    186         testInvalidData("<osm version='0.6'><relation id='1' version='1'><member/></relation></osm>",
    187                 "Missing attribute 'ref' on member in relation 1. (at line 1, column 96). 101 bytes have been read");
     187        assertEquals("Missing mandatory attribute 'ref' on <nd> of way 1. (at line 1, column 87). 88 bytes have been read",
     188                testInvalidData("<osm version='0.6'><way id='1' version='1'><nd/></way></osm>").getMessage());
     189        assertEquals("Missing attribute 'ref' on member in relation 1. (at line 1, column 96). 101 bytes have been read",
     190                testInvalidData("<osm version='0.6'><relation id='1' version='1'><member/></relation></osm>").getMessage());
    188191    }
    189192
     
    194197    @Test
    195198    void testIllegalRef() throws Exception {
    196         testInvalidData("<osm version='0.6'><way id='1' version='1'><nd ref='0'/></way></osm>",
    197                 "Illegal value of attribute 'ref' of element <nd>. Got 0. (at line 1, column 95). 96 bytes have been read");
    198         testInvalidData("<osm version='0.6'><way id='1' version='1'><nd ref='nan'/></way></osm>",
    199                 "Illegal long value for attribute 'ref'. Got 'nan'. (at line 1, column 97). 98 bytes have been read");
    200 
    201         testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='node' ref='0'/></relation></osm>",
    202                 "Incomplete <member> specification with ref=0 (at line 1, column 116). 121 bytes have been read");
    203         testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='node' ref='nan'/></relation></osm>",
    204                 "Illegal value for attribute 'ref' on member in relation 1. Got nan (at line 1, column 118). 123 bytes have been read");
     199        assertEquals("Illegal value of attribute 'ref' of element <nd>. Got 0. (at line 1, column 95). 96 bytes have been read",
     200                testInvalidData("<osm version='0.6'><way id='1' version='1'><nd ref='0'/></way></osm>").getMessage());
     201        assertEquals("Illegal long value for attribute 'ref'. Got 'nan'. (at line 1, column 97). 98 bytes have been read",
     202                testInvalidData("<osm version='0.6'><way id='1' version='1'><nd ref='nan'/></way></osm>").getMessage());
     203
     204        assertEquals("Incomplete <member> specification with ref=0 (at line 1, column 116). 121 bytes have been read",
     205                testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='node' ref='0'/></relation></osm>").getMessage());
     206        assertEquals("Illegal value for attribute 'ref' on member in relation 1. Got nan (at line 1, column 118). 123 bytes have been read",
     207                testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='node' ref='nan'/></relation></osm>")
     208                        .getMessage());
    205209    }
    206210
     
    211215    @Test
    212216    void testMissingType() throws Exception {
    213         testInvalidData("<osm version='0.6'><relation id='1' version='1'><member ref='1'/></relation></osm>",
    214                 "Missing attribute 'type' on member 1 in relation 1. (at line 1, column 104). 109 bytes have been read");
     217        assertEquals("Missing attribute 'type' on member 1 in relation 1. (at line 1, column 104). 109 bytes have been read",
     218                testInvalidData("<osm version='0.6'><relation id='1' version='1'><member ref='1'/></relation></osm>").getMessage());
    215219    }
    216220
     
    221225    @Test
    222226    void testIllegalType() throws Exception {
    223         testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='foo' ref='1'/></relation></osm>",
    224                 "Illegal value for attribute 'type' on member 1 in relation 1. Got foo. (at line 1, column 115). 120 bytes have been read");
     227        assertEquals("Illegal value for attribute 'type' on member 1 in relation 1. Got foo. (at line 1, column 115). 120 bytes have been read",
     228                testInvalidData("<osm version='0.6'><relation id='1' version='1'><member type='foo' ref='1'/></relation></osm>").getMessage());
    225229    }
    226230
     
    231235    @Test
    232236    void testMissingKeyValue() throws Exception {
    233         testInvalidData("<osm version='0.6'><node id='1' version='1'><tag/></node></osm>",
    234                 "Missing key or value attribute in tag. (at line 1, column 89). 89 bytes have been read");
    235         testInvalidData("<osm version='0.6'><node id='1' version='1'><tag k='foo'/></node></osm>",
    236                 "Missing key or value attribute in tag. (at line 1, column 97). 97 bytes have been read");
    237         testInvalidData("<osm version='0.6'><node id='1' version='1'><tag v='bar'/></node></osm>",
    238                 "Missing key or value attribute in tag. (at line 1, column 97). 97 bytes have been read");
     237        assertEquals("Missing key or value attribute in tag. (at line 1, column 89). 89 bytes have been read",
     238                testInvalidData("<osm version='0.6'><node id='1' version='1'><tag/></node></osm>").getMessage());
     239        assertEquals("Missing key or value attribute in tag. (at line 1, column 97). 97 bytes have been read",
     240                testInvalidData("<osm version='0.6'><node id='1' version='1'><tag k='foo'/></node></osm>").getMessage());
     241        assertEquals("Missing key or value attribute in tag. (at line 1, column 97). 97 bytes have been read",
     242                testInvalidData("<osm version='0.6'><node id='1' version='1'><tag v='bar'/></node></osm>").getMessage());
    239243    }
    240244
     
    245249    @Test
    246250    void testMissingVersion() throws Exception {
    247         testInvalidData("<osm/>",
    248                 "Missing mandatory attribute 'version'. (at line 1, column 45). 44 bytes have been read");
    249         testInvalidData("<osm version='0.6'><node id='1'/></osm>",
    250                 "Missing attribute 'version' on OSM primitive with ID 1. (at line 1, column 72). 72 bytes have been read");
     251        assertEquals("Missing mandatory attribute 'version'. (at line 1, column 45). 44 bytes have been read",
     252                testInvalidData("<osm/>").getMessage());
     253        assertEquals("Missing attribute 'version' on OSM primitive with ID 1. (at line 1, column 72). 72 bytes have been read",
     254                testInvalidData("<osm version='0.6'><node id='1'/></osm>").getMessage());
    251255    }
    252256
     
    257261    @Test
    258262    void testUnsupportedVersion() throws Exception {
    259         testInvalidData("<osm version='0.1'/>",
    260                 "Unsupported version: 0.1 (at line 1, column 59). 58 bytes have been read");
     263        assertEquals("Unsupported version: 0.1 (at line 1, column 59). 58 bytes have been read",
     264                testInvalidData("<osm version='0.1'/>").getMessage());
    261265    }
    262266
     
    267271    @Test
    268272    void testIllegalVersion() throws Exception {
    269         testInvalidData("<osm version='0.6'><node id='1' version='nan'/></osm>",
    270                 "Illegal value for attribute 'version' on OSM primitive with ID 1. Got nan. (at line 1, column 86). 86 bytes have been read");
     273        assertEquals("Illegal value for attribute 'version' on OSM primitive with ID 1. " +
     274                        "Got nan. (at line 1, column 86). 86 bytes have been read",
     275                testInvalidData("<osm version='0.6'><node id='1' version='nan'/></osm>").getMessage());
    271276    }
    272277
     
    277282    @Test
    278283    void testIllegalChangeset() throws Exception {
    279         testInvalidData("<osm version='0.6'><node id='1' version='1' changeset='nan'/></osm>",
    280                 "Illegal value for attribute 'changeset'. Got nan. (at line 1, column 100). 100 bytes have been read");
    281         testInvalidData("<osm version='0.6'><node id='1' version='1' changeset='-1'/></osm>",
    282                 "Illegal value for attribute 'changeset'. Got -1. (at line 1, column 99). 99 bytes have been read");
     284        assertEquals("Illegal value for attribute 'changeset'. Got nan. (at line 1, column 100). 100 bytes have been read",
     285                testInvalidData("<osm version='0.6'><node id='1' version='1' changeset='nan'/></osm>").getMessage());
     286        assertEquals("Illegal value for attribute 'changeset'. Got -1. (at line 1, column 99). 99 bytes have been read",
     287                testInvalidData("<osm version='0.6'><node id='1' version='1' changeset='-1'/></osm>").getMessage());
    283288    }
    284289
    285290    /**
    286291     * Test GDPR-compliant changeset.
    287      * @throws Exception if any error occurs
    288      */
    289     @Test
    290     void testGdprChangeset() throws Exception {
     292     * @param options The options to test
     293     * @throws Exception if any error occurs
     294     */
     295    @ParameterizedTest
     296    @MethodSource("options")
     297    void testGdprChangeset(Options... options) throws Exception {
    291298        String gdprChangeset = "<osm version='0.6'><node id='1' version='1' changeset='0'/></osm>";
    292         for (Options[] options : options()) {
    293             testValidData(gdprChangeset, options);
    294         }
     299        testValidData(gdprChangeset, options);
    295300    }
    296301
     
    301306    @Test
    302307    void testInvalidBounds() throws Exception {
    303         testInvalidData("<osm version='0.6'><bounds/></osm>",
    304                 "Missing mandatory attributes on element 'bounds'. " +
    305                 "Got minlon='null',minlat='null',maxlon='null',maxlat='null', origin='null'. (at line 1, column 67). 72 bytes have been read");
    306         testInvalidData("<osm version='0.6'><bounds minlon='0'/></osm>",
    307                 "Missing mandatory attributes on element 'bounds'. " +
    308                 "Got minlon='0',minlat='null',maxlon='null',maxlat='null', origin='null'. (at line 1, column 78). 83 bytes have been read");
    309         testInvalidData("<osm version='0.6'><bounds minlon='0' minlat='0'/></osm>",
    310                 "Missing mandatory attributes on element 'bounds'. " +
    311                 "Got minlon='0',minlat='0',maxlon='null',maxlat='null', origin='null'. (at line 1, column 89). 94 bytes have been read");
    312         testInvalidData("<osm version='0.6'><bounds minlon='0' minlat='0' maxlon='1'/></osm>",
    313                 "Missing mandatory attributes on element 'bounds'. " +
    314                 "Got minlon='0',minlat='0',maxlon='1',maxlat='null', origin='null'. (at line 1, column 100). 105 bytes have been read");
     308        assertEquals("Missing mandatory attributes on element 'bounds'. " +
     309                "Got minlon='null',minlat='null',maxlon='null',maxlat='null', origin='null'. (at line 1, column 67). 72 bytes have been read",
     310                testInvalidData("<osm version='0.6'><bounds/></osm>").getMessage());
     311        assertEquals("Missing mandatory attributes on element 'bounds'. " +
     312                "Got minlon='0',minlat='null',maxlon='null',maxlat='null', origin='null'. (at line 1, column 78). 83 bytes have been read",
     313                testInvalidData("<osm version='0.6'><bounds minlon='0'/></osm>").getMessage());
     314        assertEquals("Missing mandatory attributes on element 'bounds'. " +
     315                "Got minlon='0',minlat='0',maxlon='null',maxlat='null', origin='null'. (at line 1, column 89). 94 bytes have been read",
     316                testInvalidData("<osm version='0.6'><bounds minlon='0' minlat='0'/></osm>").getMessage());
     317        assertEquals("Missing mandatory attributes on element 'bounds'. " +
     318                "Got minlon='0',minlat='0',maxlon='1',maxlat='null', origin='null'. (at line 1, column 100). 105 bytes have been read",
     319                testInvalidData("<osm version='0.6'><bounds minlon='0' minlat='0' maxlon='1'/></osm>").getMessage());
    315320    }
    316321
     
    367372    /**
    368373     * Test reading remark from Overpass API.
    369      * @throws Exception if any error occurs
    370      */
    371     @Test
    372     void testRemark() throws Exception {
     374     * @param options The options to test
     375     * @throws Exception if any error occurs
     376     */
     377    @ParameterizedTest
     378    @MethodSource("options")
     379    void testRemark(Options... options) throws Exception {
    373380        String query = "<osm version=\"0.6\" generator=\"Overpass API 0.7.55.4 3079d8ea\">\r\n" +
    374381                "<note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note>\r\n" +
     
    376383                "<remark>runtime error: Query ran out of memory in \"query\" at line 5.</remark>\r\n" +
    377384                "</osm>";
    378         for (Options[] options : options()) {
    379             DataSet ds = testValidData(query, options);
    380             assertEquals("runtime error: Query ran out of memory in \"query\" at line 5.", ds.getRemark());
    381         }
     385        DataSet ds = testValidData(query, options);
     386        assertEquals("runtime error: Query ran out of memory in \"query\" at line 5.", ds.getRemark());
    382387    }
    383388
    384389    /**
    385390     * Test reading a file with unknown attributes in osm primitives
    386      * @throws Exception if any error occurs
    387      */
    388     @Test
    389     void testUnknownAttributeTags() throws Exception {
     391     * @param options The options to test
     392     * @throws Exception if any error occurs
     393     */
     394    @ParameterizedTest
     395    @MethodSource("options")
     396    void testUnknownAttributeTags(Options... options) throws Exception {
    390397        String testData = "<osm version=\"0.6\" generator=\"fake generator\">"
    391398                + "<node id='1' version='1' visible='true' changeset='82' randomkey='randomvalue'></node>" + "</osm>";
    392         for (Options[] options : options()) {
    393             DataSet ds = testValidData(testData, options);
    394             Node firstNode = ds.getNodes().iterator().next();
    395             if (Arrays.asList(options).contains(Options.CONVERT_UNKNOWN_TO_TAGS)) {
    396                 assertEquals("randomvalue", firstNode.get("randomkey"));
    397             } else {
    398                 assertNull(firstNode.get("randomkey"));
    399             }
    400             if (Arrays.asList(options).contains(Options.SAVE_ORIGINAL_ID)) {
    401                 assertEquals("1", firstNode.get("current_id"));
    402             } else {
    403                 assertNull(firstNode.get("current_id"));
    404             }
    405         }
     399        DataSet ds = testValidData(testData, options);
     400        Node firstNode = ds.getNodes().iterator().next();
     401        if (options != null && Arrays.asList(options).contains(Options.CONVERT_UNKNOWN_TO_TAGS)) {
     402            assertEquals("randomvalue", firstNode.get("randomkey"));
     403        } else {
     404            assertNull(firstNode.get("randomkey"));
     405        }
     406        if (options != null && Arrays.asList(options).contains(Options.SAVE_ORIGINAL_ID)) {
     407            assertEquals("1", firstNode.get("current_id"));
     408        } else {
     409            assertNull(firstNode.get("current_id"));
     410        }
     411    }
     412
     413    @ParameterizedTest
     414    @ValueSource(strings = {
     415            "<osm version=\"0.6\"><error>Mismatch in tags key and value size</error></osm>",
     416            "<osm version=\"0.6\"><node id=\"1\" visible=\"true\" version=\"1\"><error>Mismatch in tags key and value size</error></node></osm>",
     417            "<osm version=\"0.6\"><way id=\"1\" visible=\"true\" version=\"1\"><error>Mismatch in tags key and value size</error></way></osm>",
     418            "<osm version=\"0.6\"><way id=\"1\" visible=\"true\" version=\"1\"><error>Mismatch in tags key and value size</error></way></osm>",
     419            "<osm version=\"0.6\"><relation id=\"1\" visible=\"true\" version=\"1\"><error>Mismatch in tags key and value size</error>" +
     420                    "</relation></osm>"
     421    })
     422    void testErrorMessage(String testData) throws Exception {
     423        IllegalDataException illegalDataException = testInvalidData(testData);
     424        assertTrue(illegalDataException.getMessage().contains("Mismatch in tags key and value size"));
     425    }
     426
     427    @ParameterizedTest
     428    @ValueSource(strings = {
     429            "<osm version=\"0.6\"><error></error></osm>",
     430            "<osm version=\"0.6\"><node id=\"1\" visible=\"true\" version=\"1\"><error/></node></osm>",
     431            "<osm version=\"0.6\"><way id=\"1\" visible=\"true\" version=\"1\"><error></error></way></osm>",
     432            "<osm version=\"0.6\"><way id=\"1\" visible=\"true\" version=\"1\"><error></error></way></osm>",
     433            "<osm version=\"0.6\"><relation id=\"1\" visible=\"true\" version=\"1\"><error/></relation></osm>"
     434    })
     435    void testErrorNoMessage(String testData) throws Exception {
     436        IllegalDataException illegalDataException = testInvalidData(testData);
     437        assertTrue(illegalDataException.getMessage().contains("Unknown error element type"));
    406438    }
    407439}
Note: See TracChangeset for help on using the changeset viewer.