Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions rpm/src/main/java/org/eclipse/packager/rpm/RpmVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import java.util.Objects;
import java.util.Optional;

import static org.eclipse.packager.rpm.RpmVersionValidator.validateEVR;
import static org.eclipse.packager.rpm.RpmVersionValidator.validateEpoch;
import static org.eclipse.packager.rpm.RpmVersionValidator.validateVersion;

public class RpmVersion implements Comparable<RpmVersion> {
private final Optional<Integer> epoch;

Expand All @@ -32,15 +36,15 @@ public RpmVersion(final String version, final String release) {
}

public RpmVersion(final Integer epoch, final String version, final String release) {
this.epoch = Optional.ofNullable(epoch);
this.version = Objects.requireNonNull(version);
this.release = Optional.ofNullable(release);
this(Optional.ofNullable(epoch), version, Optional.ofNullable(release));
}

public RpmVersion(final Optional<Integer> epoch, final String version, final Optional<String> release) {
this.epoch = Objects.requireNonNull(epoch);
this.version = Objects.requireNonNull(version);
validateVersion(this.version);
this.release = Objects.requireNonNull(release);
this.release.ifPresent(RpmVersionValidator::validateRelease);
}

public Optional<Integer> getEpoch() {
Expand Down Expand Up @@ -75,12 +79,16 @@ public static RpmVersion valueOf(final String version) {
return null;
}

validateEVR(version);

final String[] toks1 = version.split(":", 2);

final String n;
Integer epoch = null;
if (toks1.length > 1) {
epoch = Integer.parseInt(toks1[0]);
final String epochStr = toks1[0];
validateEpoch(epochStr);
epoch = Integer.parseInt(epochStr);
n = toks1[1];
} else {
n = toks1[0];
Expand Down
38 changes: 9 additions & 29 deletions rpm/src/main/java/org/eclipse/packager/rpm/RpmVersionScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,17 @@
import java.util.NoSuchElementException;
import java.util.Objects;

final class RpmVersionScanner implements Iterator<CharSequence> {
private static final char TILDE_CHAR = '~';
import static org.eclipse.packager.rpm.RpmVersionValidator.ALPHA;
import static org.eclipse.packager.rpm.RpmVersionValidator.CARAT_CHAR;
import static org.eclipse.packager.rpm.RpmVersionValidator.DIGIT;
import static org.eclipse.packager.rpm.RpmVersionValidator.SIGNIFICANT;
import static org.eclipse.packager.rpm.RpmVersionValidator.TILDE_CHAR;

final class RpmVersionScanner implements Iterator<CharSequence> {
private static final String TILDE_STRING = "~";

private static final char CARAT_CHAR = '^';

private static final String CARAT_STRING = "^";

private static final BitSet ALPHA = new BitSet(128);

static {
ALPHA.set('A', 'Z');
ALPHA.set('a', 'z');
}

private static final BitSet DIGIT = new BitSet(128);

static {
DIGIT.set('0', '9');
}

private static final BitSet SIGNIFICANT = new BitSet(128);

static {
SIGNIFICANT.or(ALPHA);
SIGNIFICANT.or(DIGIT);
SIGNIFICANT.set(TILDE_CHAR);
SIGNIFICANT.set(CARAT_CHAR);
}

private final CharBuffer buf;

private int position;
Expand Down Expand Up @@ -117,11 +97,11 @@ private void skipInsignificantChars() {
}
}

private boolean hasNext(BitSet bitSet) {
private boolean hasNext(final BitSet bitSet) {
return (hasNext() && bitSet.get(buf.charAt(position)));
}

private boolean hasNext(char c) {
private boolean hasNext(final char c) {
return (hasNext() && buf.charAt(position) == c);
}

Expand All @@ -135,7 +115,7 @@ private int skipLeadingZeros() {
return start;
}

private CharBuffer next(BitSet bitSet) {
private CharBuffer next(final BitSet bitSet) {
skipInsignificantChars();

final int start = skipLeadingZeros();
Expand Down
151 changes: 151 additions & 0 deletions rpm/src/main/java/org/eclipse/packager/rpm/RpmVersionValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2015, 2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.packager.rpm;

import java.util.BitSet;

public final class RpmVersionValidator {
private static final String DOT_DOT = "..";

private static final int NBITS = 128;

static final char TILDE_CHAR = '~';

static final char CARAT_CHAR = '^';

static final BitSet ALPHA = new BitSet(NBITS);

static {
ALPHA.set('A', 'Z' + 1);
ALPHA.set('a', 'z' + 1);
}

static final BitSet DIGIT = new BitSet(NBITS);

static {
DIGIT.set('0', '9' + 1);
}

private static final BitSet ALPHANUM = new BitSet(NBITS);

static {
ALPHANUM.or(ALPHA);
ALPHANUM.or(DIGIT);
}

static final BitSet SIGNIFICANT = new BitSet(NBITS);

static {
SIGNIFICANT.or(ALPHANUM);
SIGNIFICANT.set(TILDE_CHAR);
SIGNIFICANT.set(CARAT_CHAR);
}

private static final BitSet NAME = new BitSet(NBITS);

static {
NAME.or(ALPHANUM);
NAME.set('.');
NAME.set('-');
NAME.set('_');
NAME.set('+');
NAME.set('%');
NAME.set('{');
NAME.set('}');
}

private static final BitSet FIRST_CHARS_NAME = new BitSet(NBITS);

static {
FIRST_CHARS_NAME.or(ALPHANUM);
FIRST_CHARS_NAME.set('_');
FIRST_CHARS_NAME.set('%');
}

private static final BitSet VERREL = new BitSet(NBITS);

static {
VERREL.or(SIGNIFICANT);
VERREL.set('.');
VERREL.set('_');
VERREL.set('+');
}

private static final BitSet EVR = new BitSet(NBITS);

static {
EVR.or(VERREL);
EVR.set('-');
EVR.set(':');
}

private RpmVersionValidator() {

}

public static void validateName(final String name) {
validateChars(name, NAME, FIRST_CHARS_NAME);
}

public static void validateEpoch(final String epoch) {
validateChars(epoch, DIGIT);
}

public static void validateVersion(final String version) {
validateChars(version, VERREL);
}

public static void validateRelease(final String release) {
validateVersion(release);
}

public static void validateEVR(final String evr) {
validateChars(evr, EVR);
}

private static void validateChars(final String field, final BitSet allowedChars) {
validateChars(field, allowedChars, null);
}

private static void validateChars(final String field, final BitSet allowedChars, final BitSet allowedFirstChars) {
final int start;

if (allowedFirstChars == null) {
start = 0;
} else {
final char c = field.charAt(0);

if (!allowedFirstChars.get(c)) {
throw new IllegalArgumentException("Illegal char '" + c + "' (0x" + Integer.toHexString(c) + ") in '" + field + "'");
}

start = 1;
}

final int length = field.length();

for (int i = start; i < length; i++) {
final char c = field.charAt(i);
final boolean allowed = allowedChars.get(c);

if (!allowed) {
throw new IllegalArgumentException("Illegal char '" + c + "' (0x" + Integer.toHexString(c) + ") in '" + field + "'");
}
}

if (field.contains(DOT_DOT)) {
throw new IllegalArgumentException("Illegal sequence '..' in '" + field + "'");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import static java.util.Comparator.comparing;
import static java.util.Optional.of;
import static org.eclipse.packager.rpm.RpmVersionValidator.validateName;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -653,6 +654,7 @@ private static BuilderOptions makeBuilderOptions(final OpenOption[] openOptions)

public RpmBuilder(final String name, final RpmVersion version, final String architecture, final Path targetFile, final BuilderOptions options) throws IOException {
this.name = name;
validateName(name);
this.version = version;
this.architecture = architecture;

Expand Down
97 changes: 93 additions & 4 deletions rpm/src/test/java/org/eclipse/packager/rpm/VersionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,112 @@

package org.eclipse.packager.rpm;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.packager.rpm.RpmVersionValidator.validateName;

class VersionTest {
@Test
void testName() {
assertThatCode(() -> validateName("foo")).doesNotThrowAnyException();
assertThatThrownBy(() -> validateName("~foo")).isExactlyInstanceOf(IllegalArgumentException.class).hasMessage("Illegal char '~' (0x7e) in '~foo'");
assertThatThrownBy(() -> validateName("foo\0")).isExactlyInstanceOf(IllegalArgumentException.class).hasMessage("Illegal char '\0' (0x0) in 'foo\0'");
assertThatThrownBy(() -> validateName("€foo")).isExactlyInstanceOf(IllegalArgumentException.class).hasMessage("Illegal char '€' (0x20ac) in '€foo'");
}

@Test
void testRpmVersion() {
final RpmVersion v = new RpmVersion("1.0");
assertThat(v.getEpoch()).isEmpty();
assertThat(v.getVersion()).isEqualTo("1.0");
assertThat(v.getRelease()).isEmpty();
assertThat(v).hasToString("1.0");
}

@Test
void testRpmVersionWithRelease() {
final RpmVersion v = new RpmVersion("1.0", "1");
assertThat(v.getEpoch()).isEmpty();
assertThat(v.getVersion()).isEqualTo("1.0");
assertThat(v.getRelease()).hasValue("1");
assertThat(v).hasToString("1.0-1");
}

@Test
void testRpmVersionWithEmptyRelease() {
final RpmVersion v = new RpmVersion("1.0", "");
assertThat(v.getEpoch()).isEmpty();
assertThat(v.getVersion()).isEqualTo("1.0");
assertThat(v.getRelease()).hasValue("");
assertThat(v).hasToString("1.0");
}

@Test
void testEquals() {
final RpmVersion v1 = new RpmVersion("1.0");
final RpmVersion v2 = new RpmVersion(0, "1.0", null);
final RpmVersion v3 = new RpmVersion("2.0");
final RpmVersion v4 = new RpmVersion("1.0", "1");
final RpmVersion v5 = new RpmVersion("1.0", "2");
final RpmVersion v6 = new RpmVersion(1, "1.0", "2");
assertThat(v1).isNotEqualTo(null).isNotEqualTo("").isNotEqualTo(v2).isNotEqualTo(v3).isNotEqualTo(v4);
assertThat(v4).isNotEqualTo(v5);
assertThat(v5).isNotEqualTo(v6);
}

@Test
void testRpmVersionNull() {
assertThat(RpmVersion.valueOf(null)).isNull();
assertThat(RpmVersion.valueOf("")).isNull();
}

@ParameterizedTest
@CsvSource(value = {"1.2.3,,1.2.3,", "0:1.2.3,0,1.2.3,", "0:1.2.3-1,0,1.2.3,1", "1.2.3-1,,1.2.3,1", "1.2.3-123-456,,1.2.3,123-456"})
@CsvSource(value = {"1.2.3,,1.2.3,", "0:1.2.3,0,1.2.3,", "0:1.2.3-1,0,1.2.3,1", "1.2.3-1,,1.2.3,1"})
void testVersion(final String version, final Integer expectedEpoch, final String expectedVersion, final String expectedRelease) {
final RpmVersion v = RpmVersion.valueOf(version);
assertThat(v.getEpoch()).isEqualTo(Optional.ofNullable(expectedEpoch));
assertThat(v.getVersion()).isEqualTo(expectedVersion);
assertThat(v.getRelease()).isEqualTo(Optional.ofNullable(expectedRelease));
assertThat(v).hasToString(version);
}

@ParameterizedTest
@ValueSource(strings = {"1-2-3\n", "A:1.2.3", "1.2.3-123-456", "1..2"})
void testInvalidVersion(final String version) {
assertThatThrownBy(() -> RpmVersion.valueOf(version)).isExactlyInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("Illegal ");
}

@Test
void testRpmScanner() {
final RpmVersionScanner scanner = new RpmVersionScanner("1.0");
assertThat(scanner.hasNext()).isTrue();
assertThat(scanner.next()).asString().isEqualTo("1");
assertThat(scanner.hasNext()).isTrue();
assertThat(scanner.next()).asString().isEqualTo("0");
assertThat(scanner.hasNext()).isFalse();
assertThatThrownBy(scanner::next).isExactlyInstanceOf(NoSuchElementException.class);
}

@Test
void testRpmScannerTokens() {
final RpmVersionScanner scanner = new RpmVersionScanner("2.0.1");
final Spliterator<CharSequence> spliterator = Spliterators.spliteratorUnknownSize(scanner, Spliterator.ORDERED);
final List<String> tokens = StreamSupport.stream(spliterator, false).map(CharSequence::toString).collect(Collectors.toList());
assertThat(tokens).containsExactly("2" , "0", "1");
}

@ParameterizedTest
Expand Down