001/*
002 * This file is part of the JDrupes non-blocking HTTP Codec
003 * Copyright (C) 2016, 2017  Michael N. Lipp
004 *
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Lesser General Public License as published
007 * by the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
013 * License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.httpcodec.util;
020
021import java.io.UnsupportedEncodingException;
022import java.net.URLDecoder;
023import java.nio.ByteBuffer;
024import java.util.HashMap;
025import java.util.Map;
026
027/**
028 * A decoder for URL-encoded form data.
029 */
030public class FormUrlDecoder {
031
032        private Map<String,String> fields = new HashMap<>();
033        private String rest = "";
034
035        /**
036         * Add the data in the buffer to the form data. May be invoked
037         * several times if the form data is split across several buffers.
038         * 
039         * @param buf the buffer with the data
040         */
041        public void addData(ByteBuffer buf) {
042                try {
043                        String data;
044                        if (buf.hasArray()) {
045                                data = rest + new String(buf.array(),
046                                                buf.arrayOffset() + buf.position(), buf.remaining(), "ascii");
047                        } else {
048                                byte[] bc = new byte[buf.remaining()];
049                                buf.get(bc);
050                                data = rest + new String(bc, "ascii");
051                        }
052                        buf.position(buf.limit()); // for consistency
053                        int oldPos = 0;
054                        while (true) {
055                                int newPos = data.indexOf('&', oldPos);
056                                if (newPos < 0) {
057                                        rest = data.substring(oldPos);
058                                        break;
059                                }
060                                split(data, oldPos, newPos);
061                                oldPos = newPos + 1;
062                        }
063                } catch (UnsupportedEncodingException e) {
064                        // Using only built-in encodings
065                        e.printStackTrace();
066                }
067        }
068
069        private void split(String pairString, int pairStart, int pairEnd) {
070                int eqPos = pairString.indexOf('=', pairStart);
071                if (eqPos < 0) {
072                        return;
073                }
074                try {
075                        fields.put(URLDecoder.decode(pairString.substring(pairStart, eqPos),
076                                "utf-8"),
077                                URLDecoder.decode(pairString.substring(eqPos + 1, pairEnd),
078                                        "utf-8"));
079                } catch (UnsupportedEncodingException e) {
080                        // Using only built-in encodings
081                }
082        }
083
084        /**
085         * Return the fields decoded from the data that has been added
086         * by {@link #addData(ByteBuffer)}. Invoking this method terminates
087         * the decoding, i.e. {@link #addData(ByteBuffer)} should not be
088         * called again after this method has been invoked.
089         * 
090         * @return the decoded fields
091         */
092        public Map<String,String> fields() {
093                split(rest, 0, rest.length());
094                rest = "";
095                return fields;
096        }
097}