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}