1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.hdfs.server.datanode.web.webhdfs; 19 20 import io.netty.handler.codec.http.QueryStringDecoder; 21 import org.apache.commons.io.Charsets; 22 import org.apache.hadoop.conf.Configuration; 23 import org.apache.hadoop.fs.permission.FsPermission; 24 import org.apache.hadoop.hdfs.HAUtil; 25 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 26 import org.apache.hadoop.hdfs.web.resources.BlockSizeParam; 27 import org.apache.hadoop.hdfs.web.resources.BufferSizeParam; 28 import org.apache.hadoop.hdfs.web.resources.DelegationParam; 29 import org.apache.hadoop.hdfs.web.resources.DoAsParam; 30 import org.apache.hadoop.hdfs.web.resources.HttpOpParam; 31 import org.apache.hadoop.hdfs.web.resources.LengthParam; 32 import org.apache.hadoop.hdfs.web.resources.NamenodeAddressParam; 33 import org.apache.hadoop.hdfs.web.resources.OffsetParam; 34 import org.apache.hadoop.hdfs.web.resources.OverwriteParam; 35 import org.apache.hadoop.hdfs.web.resources.PermissionParam; 36 import org.apache.hadoop.hdfs.web.resources.ReplicationParam; 37 import org.apache.hadoop.hdfs.web.resources.UserParam; 38 import org.apache.hadoop.security.SecurityUtil; 39 import org.apache.hadoop.security.token.Token; 40 41 import java.io.IOException; 42 import java.net.URI; 43 import java.nio.charset.Charset; 44 import java.util.List; 45 import java.util.Map; 46 47 import static org.apache.hadoop.hdfs.protocol.HdfsConstants.HDFS_URI_SCHEME; 48 import static org.apache.hadoop.hdfs.server.datanode.web.webhdfs.WebHdfsHandler.WEBHDFS_PREFIX_LENGTH; 49 50 class ParameterParser { 51 private final Configuration conf; 52 private final String path; 53 private final Map<String, List<String>> params; 54 ParameterParser(QueryStringDecoder decoder, Configuration conf)55 ParameterParser(QueryStringDecoder decoder, Configuration conf) { 56 this.path = decodeComponent(decoder.path().substring 57 (WEBHDFS_PREFIX_LENGTH), Charsets.UTF_8); 58 this.params = decoder.parameters(); 59 this.conf = conf; 60 } 61 path()62 String path() { return path; } 63 op()64 String op() { 65 return param(HttpOpParam.NAME); 66 } 67 offset()68 long offset() { 69 return new OffsetParam(param(OffsetParam.NAME)).getOffset(); 70 } 71 length()72 long length() { 73 return new LengthParam(param(LengthParam.NAME)).getLength(); 74 } 75 namenodeId()76 String namenodeId() { 77 return new NamenodeAddressParam(param(NamenodeAddressParam.NAME)) 78 .getValue(); 79 } 80 doAsUser()81 String doAsUser() { 82 return new DoAsParam(param(DoAsParam.NAME)).getValue(); 83 } 84 userName()85 String userName() { 86 return new UserParam(param(UserParam.NAME)).getValue(); 87 } 88 bufferSize()89 int bufferSize() { 90 return new BufferSizeParam(param(BufferSizeParam.NAME)).getValue(conf); 91 } 92 blockSize()93 long blockSize() { 94 return new BlockSizeParam(param(BlockSizeParam.NAME)).getValue(conf); 95 } 96 replication()97 short replication() { 98 return new ReplicationParam(param(ReplicationParam.NAME)).getValue(conf); 99 } 100 permission()101 FsPermission permission() { 102 return new PermissionParam(param(PermissionParam.NAME)).getFsPermission(); 103 } 104 overwrite()105 boolean overwrite() { 106 return new OverwriteParam(param(OverwriteParam.NAME)).getValue(); 107 } 108 delegationToken()109 Token<DelegationTokenIdentifier> delegationToken() throws IOException { 110 String delegation = param(DelegationParam.NAME); 111 final Token<DelegationTokenIdentifier> token = new 112 Token<DelegationTokenIdentifier>(); 113 token.decodeFromUrlString(delegation); 114 URI nnUri = URI.create(HDFS_URI_SCHEME + "://" + namenodeId()); 115 boolean isLogical = HAUtil.isLogicalUri(conf, nnUri); 116 if (isLogical) { 117 token.setService(HAUtil.buildTokenServiceForLogicalUri(nnUri, 118 HDFS_URI_SCHEME)); 119 } else { 120 token.setService(SecurityUtil.buildTokenService(nnUri)); 121 } 122 return token; 123 } 124 conf()125 Configuration conf() { 126 return conf; 127 } 128 param(String key)129 private String param(String key) { 130 List<String> p = params.get(key); 131 return p == null ? null : p.get(0); 132 } 133 134 /** 135 * The following function behaves exactly the same as netty's 136 * <code>QueryStringDecoder#decodeComponent</code> except that it 137 * does not decode the '+' character as space. WebHDFS takes this scheme 138 * to maintain the backward-compatibility for pre-2.7 releases. 139 */ decodeComponent(final String s, final Charset charset)140 private static String decodeComponent(final String s, final Charset charset) { 141 if (s == null) { 142 return ""; 143 } 144 final int size = s.length(); 145 boolean modified = false; 146 for (int i = 0; i < size; i++) { 147 final char c = s.charAt(i); 148 if (c == '%' || c == '+') { 149 modified = true; 150 break; 151 } 152 } 153 if (!modified) { 154 return s; 155 } 156 final byte[] buf = new byte[size]; 157 int pos = 0; // position in `buf'. 158 for (int i = 0; i < size; i++) { 159 char c = s.charAt(i); 160 if (c == '%') { 161 if (i == size - 1) { 162 throw new IllegalArgumentException("unterminated escape sequence at" + 163 " end of string: " + s); 164 } 165 c = s.charAt(++i); 166 if (c == '%') { 167 buf[pos++] = '%'; // "%%" -> "%" 168 break; 169 } 170 if (i == size - 1) { 171 throw new IllegalArgumentException("partial escape sequence at end " + 172 "of string: " + s); 173 } 174 c = decodeHexNibble(c); 175 final char c2 = decodeHexNibble(s.charAt(++i)); 176 if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) { 177 throw new IllegalArgumentException( 178 "invalid escape sequence `%" + s.charAt(i - 1) + s.charAt( 179 i) + "' at index " + (i - 2) + " of: " + s); 180 } 181 c = (char) (c * 16 + c2); 182 // Fall through. 183 } 184 buf[pos++] = (byte) c; 185 } 186 return new String(buf, 0, pos, charset); 187 } 188 189 /** 190 * Helper to decode half of a hexadecimal number from a string. 191 * @param c The ASCII character of the hexadecimal number to decode. 192 * Must be in the range {@code [0-9a-fA-F]}. 193 * @return The hexadecimal value represented in the ASCII character 194 * given, or {@link Character#MAX_VALUE} if the character is invalid. 195 */ decodeHexNibble(final char c)196 private static char decodeHexNibble(final char c) { 197 if ('0' <= c && c <= '9') { 198 return (char) (c - '0'); 199 } else if ('a' <= c && c <= 'f') { 200 return (char) (c - 'a' + 10); 201 } else if ('A' <= c && c <= 'F') { 202 return (char) (c - 'A' + 10); 203 } else { 204 return Character.MAX_VALUE; 205 } 206 } 207 } 208