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

io.inverno.mod.http.server.internal.multipart.UrlEncodedBodyDecoder Maven / Gradle / Ivy

There is a newer version: 1.11.0
Show newest version
/*
 * Copyright 2020 Jeremy KUHN
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.inverno.mod.http.server.internal.multipart;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpConstants;
import io.inverno.core.annotation.Bean;
import io.inverno.core.annotation.Bean.Visibility;
import io.inverno.mod.base.Charsets;
import io.inverno.mod.base.converter.ObjectConverter;
import io.inverno.mod.base.resource.MediaTypes;
import io.inverno.mod.http.base.Parameter;
import io.inverno.mod.http.base.header.Headers;
import io.inverno.mod.http.base.header.Headers.ContentType;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.SignalType;

/**
 * 

* An application/x-www-form-urlencoded payload decoder implementation as defined by application/x-www-form-urlencoded. *

* * @author Jeremy Kuhn * @since 1.0 */ @Bean(visibility = Visibility.PRIVATE) public class UrlEncodedBodyDecoder implements MultipartDecoder { private ObjectConverter parameterConverter; /** *

* Creates an application/x-www-form-urlencoded body decoder. *

* * @param parameterConverter a string object converter */ public UrlEncodedBodyDecoder(ObjectConverter parameterConverter) { this.parameterConverter = parameterConverter; } @Override public Flux decode(Flux data, ContentType contentType) { if(contentType == null || !contentType.getMediaType().equalsIgnoreCase(MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED)) { throw new IllegalArgumentException("Content type is not " + MediaTypes.APPLICATION_X_WWW_FORM_URLENCODED); } return Flux.create(emitter -> { data.subscribe(new BodyDataSubscriber(contentType, emitter)); }); } private UrlEncodedParameter readParameter(ByteBuf buffer, Charset charset) throws MalformedBodyException { if (charset == null) { charset = HttpConstants.DEFAULT_CHARSET; } int readerIndex = buffer.readerIndex(); Integer startIndex = null; Integer endIndex = null; String parameterName = null; while (buffer.isReadable()) { byte nextByte = buffer.readByte(); if (nextByte == HttpConstants.CR) { if(buffer.isReadable()) { if (buffer.readByte() == HttpConstants.LF) { endIndex = buffer.readerIndex() - 2; if (parameterName != null) { return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(parameterName, charset), this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), false, true); } else if (startIndex != null) { return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), "", false, true); } return new UrlEncodedParameter(this.parameterConverter, "", "", true, true); } else { buffer.readerIndex(readerIndex); throw new MalformedBodyException("Bad end of line"); } } } else if (nextByte == HttpConstants.LF) { endIndex = buffer.readerIndex() - 1; if (parameterName != null) { return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(parameterName, charset), this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), false, true); } else if (startIndex != null) { return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), "", false, true); } return new UrlEncodedParameter(this.parameterConverter, "", "", true, true); } else { if (parameterName == null) { if (startIndex == null) { startIndex = buffer.readerIndex() - 1; } if (nextByte == '=') { endIndex = buffer.readerIndex() - 1; parameterName = buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(); startIndex = endIndex = null; } else if (nextByte == '&') { if(startIndex < buffer.readerIndex() - 1) { endIndex = buffer.readerIndex() - 1; return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), "", false, false); } else { startIndex = null; } } } else { if (startIndex == null) { startIndex = buffer.readerIndex() - 1; } if (nextByte == '&') { endIndex = buffer.readerIndex() - 1; return new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(parameterName, charset), this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), false, false); } } } } if(parameterName != null) { if(startIndex == null) { UrlEncodedParameter partialParameter = new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(parameterName, charset), "", true, false); buffer.readerIndex(readerIndex); return partialParameter; } else { endIndex = buffer.readerIndex(); UrlEncodedParameter partialParameter = new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(parameterName, charset), this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), true, false); buffer.readerIndex(readerIndex); return partialParameter; } } else { if(startIndex != null && startIndex < buffer.readerIndex()) { endIndex = buffer.readerIndex(); UrlEncodedParameter partialParameter = new UrlEncodedParameter(this.parameterConverter, this.decodeComponent(buffer.getCharSequence(startIndex, endIndex - startIndex, charset).toString(), charset), "", true, false); buffer.readerIndex(readerIndex); return partialParameter; } } buffer.readerIndex(readerIndex); return null; } private String decodeComponent(String value, Charset charset) throws MalformedBodyException { try { return URLDecoder.decode(value, charset.toString()); // RFC-3986 2 } catch (IllegalArgumentException | UnsupportedEncodingException e) { throw new MalformedBodyException(e); } } /** *

* Request data publisher subscriber. *

* * @author Jeremy Kuhn * @since 1.0 */ private class BodyDataSubscriber extends BaseSubscriber { private final Charset charset; private final FluxSink emitter; private ByteBuf keepBuffer; private UrlEncodedParameter partialParameter; public BodyDataSubscriber(Headers.ContentType contentType, FluxSink emitter) { this.charset = Charsets.orDefault(contentType.getCharset()); this.emitter = emitter; this.emitter.onCancel(() -> this.cancel()); } private void emitPartialParameter() { if(this.partialParameter != null && !this.partialParameter.isLast()) { this.partialParameter.setPartial(false); this.partialParameter.setLast(true); this.emitter.next(this.partialParameter); } } @Override protected void hookOnNext(ByteBuf value) { try { final ByteBuf buffer; if(this.keepBuffer != null && this.keepBuffer.isReadable()) { buffer = Unpooled.wrappedBuffer(this.keepBuffer, value); } else { buffer = value; } UrlEncodedParameter nextParameter = null; while( (nextParameter = UrlEncodedBodyDecoder.this.readParameter(buffer, this.charset)) != null ) { if(nextParameter.isLast()) { if(!nextParameter.isPartial()) { this.emitter.next(nextParameter); } if(buffer.isReadable()) { // Let's be strict: if there is more data after end of line then this is a bad request // Note that we'll cancel this subscriber so if the body data flux has more // chunk we won't be able to notify such error and if there are more data they // will be ignored this.emitter.error(new MalformedBodyException("Data received after body was fully decoded")); } else { this.emitter.complete(); } this.cancel(); return; } else if(nextParameter.isPartial()) { this.partialParameter = nextParameter; break; } else { this.emitter.next(nextParameter); } } if(buffer.isReadable()) { if(this.keepBuffer != null) { this.keepBuffer.discardReadBytes(); this.keepBuffer.writeBytes(buffer); } else { this.keepBuffer = buffer.alloc().buffer(buffer.readableBytes()); this.keepBuffer.writeBytes(buffer); } } } catch(Exception e) { this.emitter.error(e); this.cancel(); } } @Override protected void hookOnError(Throwable throwable) { this.emitPartialParameter(); this.emitter.error(throwable); } @Override protected void hookOnComplete() { this.emitPartialParameter(); if(this.keepBuffer != null && this.keepBuffer.isReadable() && this.keepBuffer.getByte(this.keepBuffer.readableBytes() - 1) == HttpConstants.CR) { this.emitter.error(new MalformedBodyException("Bad end of line")); } this.emitter.complete(); } @Override protected void hookFinally(SignalType type) { if(this.keepBuffer != null) { this.keepBuffer.release(); this.keepBuffer = null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy