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