All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.artipie.http.rq.multipart.State Maven / Gradle / Ivy

There is a newer version: v1.17.16
Show newest version
/*
 * The MIT License (MIT) Copyright (c) 2020-2023 artipie.com
 * https://github.com/artipie/artipie/blob/master/LICENSE.txt
 */
package com.artipie.http.rq.multipart;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.function.BiFunction;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;

/**
 * Multipart processor state.
 * 

* While parsing multipart request we may enter different states: *

    *
  1. Initial state, didn't start processing yet
  2. *
  3. Parsing preamble before first part started (should be ignored)
  4. *
  5. Parsing request part, between boundaries. Also, we need to know if we are *
      *
    • Just started a new part parsing to create new part with part headers accumulator
    • *
    • Finished part, to notify downstream on complete
    • *
    *
  6. *
  7. Parsing epilogue, after the last part and double dash (should be ignored)
  8. *
* This class defines all state and its transition and provide method to patch and check the state. *

* @since 1.0 */ @SuppressWarnings("PMD.AvoidAccessToStaticMembersViaThis") final class State { /** * Initial state. */ private static final int INIT = 1; /** * Processing preamble. */ private static final int PREAMBLE = 1 << 1; /** * Processing part. */ private static final int PART = 1 << 2; /** * Finished part. */ private static final int END = 1 << 3; /** * Starting part. */ private static final int START = 1 << 4; /** * Processing epilogue. */ private static final int EPILOGUE = 1 << 5; /** * Patch factories for multipart chunks. */ private static final Collection> PATCHERS = Collections.unmodifiableList( Arrays.asList( (buf, end) -> Patch.equal(State.INIT).addFlags(State.PREAMBLE), (buf, end) -> Patch.hasFlag(State.INIT).removeFlags(State.INIT), (buf, end) -> Patch.hasFlag(State.START).removeFlags(State.START), (buf, end) -> Patch.equal(State.INIT).addFlags(State.START), (buf, end) -> Patch.hasFlag(State.END).addFlags(State.START), (buf, end) -> Patch.hasFlag(State.PREAMBLE | State.END).removeFlags(State.PREAMBLE), (buf, end) -> new Patch( state -> (state & (State.END | State.EPILOGUE)) == State.END, state -> { final ByteBuffer dup = buf.duplicate(); // epilogue starts with double minus `--` seq after end of previous part int patch = state; if (dup.remaining() >= 2 && dup.get() == '-' && dup.get() == '-') { patch |= State.EPILOGUE; } return patch; } ), (buf, end) -> new Patch( state -> ((state & State.END) == State.END) != end, state -> { final int patch; if (end) { patch = state | State.END; } else { patch = state & ~State.END; } return patch; } ), (buf, end) -> new Patch(state -> (state & (State.PREAMBLE | State.EPILOGUE)) == 0) .addFlags(State.PART), (buf, end) -> new Patch(state -> (state & (State.PREAMBLE | State.EPILOGUE)) != 0) .removeFlags(State.PART) ) ); /** * Current state flags. */ private volatile int flags; /** * New init state. */ State() { this.flags = State.INIT; } @Override public String toString() { final StringBuilder res = new StringBuilder(38); if (this.hasFlag(State.INIT)) { res.append("INIT,"); } if (this.hasFlag(State.PREAMBLE)) { res.append("PREAMBLE,"); } if (this.hasFlag(State.PART)) { res.append("PART,"); } if (this.hasFlag(State.END)) { res.append("END,"); } if (this.hasFlag(State.START)) { res.append("START,"); } if (this.hasFlag(State.EPILOGUE)) { res.append("EPILOGUE,"); } return res.toString(); } /** * Patch current state with new chunk. * @param buf Next chunk * @param end End of part */ void patch(final ByteBuffer buf, final boolean end) { for (final BiFunction patcher : State.PATCHERS) { final ByteBuffer copy = buf.duplicate(); final Patch patch = patcher.apply(copy, end); if (patch.test(this.flags)) { this.flags = patch.applyAsInt(this.flags); } } } /** * Current state should be ignored, since it's either preamble or * epilogue. * @return True if ignore */ boolean shouldIgnore() { return (this.flags & (State.PREAMBLE | State.EPILOGUE)) != 0; } /** * Is in initial state. * @return True if current state is initial */ boolean isInit() { return this.hasFlag(State.INIT); } /** * Check if state is in start of the part. * @return True if in start */ boolean started() { return this.hasFlag(State.START); } /** * Check if state in end of the part. * @return True if in the end */ boolean ended() { return this.hasFlag(State.END); } /** * Check state has flag. * @param flag Flag to check * @return True if has */ private boolean hasFlag(final int flag) { return (this.flags & flag) == flag; } /** * Patch for state, it matches state to apply and update state with new flags. * @since 1.0 */ private static final class Patch implements IntPredicate, IntUnaryOperator { /** * Empty patch which match any state. */ static final Patch ANY = new Patch(state -> true); /** * Predicate to match current state. */ private final IntPredicate predicate; /** * Operator to update current state. */ private final IntUnaryOperator operator; /** * New patch with predicate and operator. * @param predicate To match current state */ Patch(final IntPredicate predicate) { this(predicate, state -> state); } /** * New patch with predicate and operator. * @param predicate To match current state * @param operator To update current state */ Patch(final IntPredicate predicate, final IntUnaryOperator operator) { this.operator = operator; this.predicate = predicate; } @Override public boolean test(final int state) { return this.predicate.test(state); } @Override public int applyAsInt(final int state) { return this.operator.applyAsInt(state); } /** * Create new patch copy, that apply flags to state. * @param flags To apply * @return State copy with new operator */ Patch addFlags(final int flags) { return new Patch(this.predicate, this.operator.andThen(state -> state | flags)); } /** * Create new patch copy, that removes flags from state. * @param flags To apply * @return State copy with new operator */ Patch removeFlags(final int flags) { return new Patch(this.predicate, this.operator.andThen(state -> state & ~flags)); } /** * Creates new patch matches states equal to provided state. * @param val Value to test against current state * @return New patch instance */ static Patch equal(final int val) { return new Patch(test -> test == val, state -> state); } /** * Creates new patch matches state by having flags. * @param flag Flag that state should have to match * @return New patch instance */ static Patch hasFlag(final int flag) { return new Patch(test -> (test & flag) == flag, state -> state); } /** * Creates new patch matches state by having no flags. * @param flag Flag that state should not have to match * @return New patch instance */ static Patch hasNoFlag(final int flag) { return new Patch(test -> (test & flag) == 0, state -> state); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy