001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hdfs.server.common;
020
021import static org.apache.hadoop.fs.CommonConfigurationKeys.DEFAULT_HADOOP_HTTP_STATIC_USER;
022import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_HTTP_STATIC_USER;
023
024import java.io.ByteArrayInputStream;
025import java.io.DataInputStream;
026import java.io.IOException;
027import java.io.UnsupportedEncodingException;
028import java.net.InetAddress;
029import java.net.InetSocketAddress;
030import java.net.Socket;
031import java.net.URL;
032import java.net.URLEncoder;
033import java.util.Arrays;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.HashMap;
037import java.util.List;
038
039import javax.servlet.ServletContext;
040import javax.servlet.http.HttpServletRequest;
041import javax.servlet.jsp.JspWriter;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.apache.hadoop.classification.InterfaceAudience;
046import org.apache.hadoop.conf.Configuration;
047import org.apache.hadoop.fs.Path;
048import org.apache.hadoop.hdfs.BlockReader;
049import org.apache.hadoop.hdfs.BlockReaderFactory;
050import org.apache.hadoop.hdfs.ClientContext;
051import org.apache.hadoop.hdfs.DFSClient;
052import org.apache.hadoop.hdfs.DFSUtil;
053import org.apache.hadoop.hdfs.RemotePeerFactory;
054import org.apache.hadoop.hdfs.net.Peer;
055import org.apache.hadoop.hdfs.net.TcpPeerServer;
056import org.apache.hadoop.hdfs.protocol.DatanodeID;
057import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
058import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
059import org.apache.hadoop.hdfs.protocol.LocatedBlock;
060import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
061import org.apache.hadoop.hdfs.protocol.datatransfer.sasl.SaslDataTransferClient;
062import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
063import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
064import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
065import org.apache.hadoop.hdfs.server.datanode.CachingStrategy;
066import org.apache.hadoop.hdfs.server.namenode.NameNode;
067import org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer;
068import org.apache.hadoop.hdfs.web.resources.DelegationParam;
069import org.apache.hadoop.hdfs.web.resources.DoAsParam;
070import org.apache.hadoop.hdfs.web.resources.UserParam;
071import org.apache.hadoop.http.HtmlQuoting;
072import org.apache.hadoop.io.IOUtils;
073import org.apache.hadoop.net.NetUtils;
074import org.apache.hadoop.security.AccessControlException;
075import org.apache.hadoop.security.SecurityUtil;
076import org.apache.hadoop.security.UserGroupInformation;
077import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
078import org.apache.hadoop.security.authentication.util.KerberosName;
079import org.apache.hadoop.security.authorize.ProxyServers;
080import org.apache.hadoop.security.authorize.ProxyUsers;
081import org.apache.hadoop.security.token.Token;
082import org.apache.hadoop.util.VersionInfo;
083
084import com.google.common.base.Charsets;
085
086@InterfaceAudience.Private
087public class JspHelper {
088  public static final String CURRENT_CONF = "current.conf";
089  public static final String DELEGATION_PARAMETER_NAME = DelegationParam.NAME;
090  public static final String NAMENODE_ADDRESS = "nnaddr";
091  static final String SET_DELEGATION = "&" + DELEGATION_PARAMETER_NAME +
092                                              "=";
093  private static final Log LOG = LogFactory.getLog(JspHelper.class);
094
095  /** Private constructor for preventing creating JspHelper object. */
096  private JspHelper() {} 
097  
098  // data structure to count number of blocks on datanodes.
099  private static class NodeRecord extends DatanodeInfo {
100    int frequency;
101
102    public NodeRecord(DatanodeInfo info, int count) {
103      super(info);
104      this.frequency = count;
105    }
106    
107    @Override
108    public boolean equals(Object obj) {
109      // Sufficient to use super equality as datanodes are uniquely identified
110      // by DatanodeID
111      return (this == obj) || super.equals(obj);
112    }
113    @Override
114    public int hashCode() {
115      // Super implementation is sufficient
116      return super.hashCode();
117    }
118  }
119
120  // compare two records based on their frequency
121  private static class NodeRecordComparator implements Comparator<NodeRecord> {
122
123    @Override
124    public int compare(NodeRecord o1, NodeRecord o2) {
125      if (o1.frequency < o2.frequency) {
126        return -1;
127      } else if (o1.frequency > o2.frequency) {
128        return 1;
129      } 
130      return 0;
131    }
132  }
133  
134  /**
135   * convenience method for canonicalizing host name.
136   * @param addr name:port or name 
137   * @return canonicalized host name
138   */
139   public static String canonicalize(String addr) {
140    // default port 1 is supplied to allow addr without port.
141    // the port will be ignored.
142    return NetUtils.createSocketAddr(addr, 1).getAddress()
143           .getCanonicalHostName();
144  }
145
146  /**
147   * A helper class that generates the correct URL for different schema.
148   *
149   */
150  public static final class Url {
151    public static String authority(String scheme, DatanodeID d) {
152      String fqdn = (d.getIpAddr() != null && !d.getIpAddr().isEmpty())?
153          canonicalize(d.getIpAddr()): 
154          d.getHostName();
155      if (scheme.equals("http")) {
156        return fqdn + ":" + d.getInfoPort();
157      } else if (scheme.equals("https")) {
158        return fqdn + ":" + d.getInfoSecurePort();
159      } else {
160        throw new IllegalArgumentException("Unknown scheme:" + scheme);
161      }
162    }
163
164    public static String url(String scheme, DatanodeID d) {
165      return scheme + "://" + authority(scheme, d);
166    }
167  }
168
169  public static DatanodeInfo bestNode(LocatedBlocks blks, Configuration conf)
170      throws IOException {
171    HashMap<DatanodeInfo, NodeRecord> map =
172      new HashMap<DatanodeInfo, NodeRecord>();
173    for (LocatedBlock block : blks.getLocatedBlocks()) {
174      DatanodeInfo[] nodes = block.getLocations();
175      for (DatanodeInfo node : nodes) {
176        NodeRecord record = map.get(node);
177        if (record == null) {
178          map.put(node, new NodeRecord(node, 1));
179        } else {
180          record.frequency++;
181        }
182      }
183    }
184    NodeRecord[] nodes = map.values().toArray(new NodeRecord[map.size()]);
185    Arrays.sort(nodes, new NodeRecordComparator());
186    return bestNode(nodes, false);
187  }
188
189  public static DatanodeInfo bestNode(LocatedBlock blk, Configuration conf)
190      throws IOException {
191    DatanodeInfo[] nodes = blk.getLocations();
192    return bestNode(nodes, true);
193  }
194
195  private static DatanodeInfo bestNode(DatanodeInfo[] nodes, boolean doRandom)
196      throws IOException {
197    if (nodes == null || nodes.length == 0) {
198      throw new IOException("No nodes contain this block");
199    }
200    int l = 0;
201    while (l < nodes.length && !nodes[l].isDecommissioned()) {
202      ++l;
203    }
204
205    if (l == 0) {
206      throw new IOException("No active nodes contain this block");
207    }
208
209    int index = doRandom ? DFSUtil.getRandom().nextInt(l) : 0;
210    return nodes[index];
211  }
212
213  public static void streamBlockInAscii(InetSocketAddress addr, String poolId,
214      long blockId, final Token<BlockTokenIdentifier> blockToken, long genStamp,
215      long blockSize, long offsetIntoBlock, long chunkSizeToView,
216      JspWriter out, final Configuration conf, DFSClient.Conf dfsConf,
217      final DFSClient dfs, final SaslDataTransferClient saslClient)
218          throws IOException {
219    if (chunkSizeToView == 0) return;
220    int amtToRead = (int)Math.min(chunkSizeToView, blockSize - offsetIntoBlock);
221      
222    DatanodeID datanodeId = new DatanodeID(addr.getAddress().getHostAddress(),
223      addr.getHostName(), poolId, addr.getPort(), 0, 0, 0);
224    BlockReader blockReader = new BlockReaderFactory(dfsConf).
225      setInetSocketAddress(addr).
226      setBlock(new ExtendedBlock(poolId, blockId, 0, genStamp)).
227      setFileName(BlockReaderFactory.getFileName(addr, poolId, blockId)).
228      setBlockToken(blockToken).
229      setStartOffset(offsetIntoBlock).
230      setLength(amtToRead).
231      setVerifyChecksum(true).
232      setClientName("JspHelper").
233      setClientCacheContext(ClientContext.getFromConf(conf)).
234      setDatanodeInfo(new DatanodeInfo(datanodeId)).
235      setCachingStrategy(CachingStrategy.newDefaultStrategy()).
236      setConfiguration(conf).
237      setRemotePeerFactory(new RemotePeerFactory() {
238        @Override
239        public Peer newConnectedPeer(InetSocketAddress addr,
240            Token<BlockTokenIdentifier> blockToken, DatanodeID datanodeId)
241            throws IOException {
242          Peer peer = null;
243          Socket sock = NetUtils.getDefaultSocketFactory(conf).createSocket();
244          try {
245            sock.connect(addr, HdfsServerConstants.READ_TIMEOUT);
246            sock.setSoTimeout(HdfsServerConstants.READ_TIMEOUT);
247            peer = TcpPeerServer.peerFromSocketAndKey(saslClient, sock, dfs,
248                blockToken, datanodeId);
249          } finally {
250            if (peer == null) {
251              IOUtils.closeSocket(sock);
252            }
253          }
254          return peer;
255        }
256      }).
257      build();
258
259    final byte[] buf = new byte[amtToRead];
260    try {
261      int readOffset = 0;
262      int retries = 2;
263      while (amtToRead > 0) {
264        int numRead = amtToRead;
265        try {
266          blockReader.readFully(buf, readOffset, amtToRead);
267        } catch (IOException e) {
268          retries--;
269          if (retries == 0)
270            throw new IOException("Could not read data from datanode");
271          continue;
272        }
273        amtToRead -= numRead;
274        readOffset += numRead;
275      }
276    } finally {
277      blockReader.close();
278    }
279    out.print(HtmlQuoting.quoteHtmlChars(new String(buf, Charsets.UTF_8)));
280  }
281
282  public static void addTableHeader(JspWriter out) throws IOException {
283    out.print("<table border=\"1\""+
284              " cellpadding=\"2\" cellspacing=\"2\">");
285    out.print("<tbody>");
286  }
287  public static void addTableRow(JspWriter out, String[] columns) throws IOException {
288    out.print("<tr>");
289    for (int i = 0; i < columns.length; i++) {
290      out.print("<td style=\"vertical-align: top;\"><B>"+columns[i]+"</B><br></td>");
291    }
292    out.print("</tr>");
293  }
294  public static void addTableRow(JspWriter out, String[] columns, int row) throws IOException {
295    out.print("<tr>");
296      
297    for (int i = 0; i < columns.length; i++) {
298      if (row/2*2 == row) {//even
299        out.print("<td style=\"vertical-align: top;background-color:LightGrey;\"><B>"+columns[i]+"</B><br></td>");
300      } else {
301        out.print("<td style=\"vertical-align: top;background-color:LightBlue;\"><B>"+columns[i]+"</B><br></td>");
302          
303      }
304    }
305    out.print("</tr>");
306  }
307  public static void addTableFooter(JspWriter out) throws IOException {
308    out.print("</tbody></table>");
309  }
310
311  public static void sortNodeList(final List<DatanodeDescriptor> nodes,
312                           String field, String order) {
313        
314    class NodeComapare implements Comparator<DatanodeDescriptor> {
315      static final int 
316        FIELD_NAME              = 1,
317        FIELD_LAST_CONTACT      = 2,
318        FIELD_BLOCKS            = 3,
319        FIELD_CAPACITY          = 4,
320        FIELD_USED              = 5,
321        FIELD_PERCENT_USED      = 6,
322        FIELD_NONDFS_USED       = 7,
323        FIELD_REMAINING         = 8,
324        FIELD_PERCENT_REMAINING = 9,
325        FIELD_ADMIN_STATE       = 10,
326        FIELD_DECOMMISSIONED    = 11,
327        SORT_ORDER_ASC          = 1,
328        SORT_ORDER_DSC          = 2;
329
330      int sortField = FIELD_NAME;
331      int sortOrder = SORT_ORDER_ASC;
332            
333      public NodeComapare(String field, String order) {
334        if (field.equals("lastcontact")) {
335          sortField = FIELD_LAST_CONTACT;
336        } else if (field.equals("capacity")) {
337          sortField = FIELD_CAPACITY;
338        } else if (field.equals("used")) {
339          sortField = FIELD_USED;
340        } else if (field.equals("nondfsused")) {
341          sortField = FIELD_NONDFS_USED;
342        } else if (field.equals("remaining")) {
343          sortField = FIELD_REMAINING;
344        } else if (field.equals("pcused")) {
345          sortField = FIELD_PERCENT_USED;
346        } else if (field.equals("pcremaining")) {
347          sortField = FIELD_PERCENT_REMAINING;
348        } else if (field.equals("blocks")) {
349          sortField = FIELD_BLOCKS;
350        } else if (field.equals("adminstate")) {
351          sortField = FIELD_ADMIN_STATE;
352        } else if (field.equals("decommissioned")) {
353          sortField = FIELD_DECOMMISSIONED;
354        } else {
355          sortField = FIELD_NAME;
356        }
357                
358        if (order.equals("DSC")) {
359          sortOrder = SORT_ORDER_DSC;
360        } else {
361          sortOrder = SORT_ORDER_ASC;
362        }
363      }
364
365      @Override
366      public int compare(DatanodeDescriptor d1,
367                         DatanodeDescriptor d2) {
368        int ret = 0;
369        switch (sortField) {
370        case FIELD_LAST_CONTACT:
371          ret = (int) (d2.getLastUpdate() - d1.getLastUpdate());
372          break;
373        case FIELD_CAPACITY:
374          long  dlong = d1.getCapacity() - d2.getCapacity();
375          ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0);
376          break;
377        case FIELD_USED:
378          dlong = d1.getDfsUsed() - d2.getDfsUsed();
379          ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0);
380          break;
381        case FIELD_NONDFS_USED:
382          dlong = d1.getNonDfsUsed() - d2.getNonDfsUsed();
383          ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0);
384          break;
385        case FIELD_REMAINING:
386          dlong = d1.getRemaining() - d2.getRemaining();
387          ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0);
388          break;
389        case FIELD_PERCENT_USED:
390          double ddbl =((d1.getDfsUsedPercent())-
391                        (d2.getDfsUsedPercent()));
392          ret = (ddbl < 0) ? -1 : ((ddbl > 0) ? 1 : 0);
393          break;
394        case FIELD_PERCENT_REMAINING:
395          ddbl =((d1.getRemainingPercent())-
396                 (d2.getRemainingPercent()));
397          ret = (ddbl < 0) ? -1 : ((ddbl > 0) ? 1 : 0);
398          break;
399        case FIELD_BLOCKS:
400          ret = d1.numBlocks() - d2.numBlocks();
401          break;
402        case FIELD_ADMIN_STATE:
403          ret = d1.getAdminState().toString().compareTo(
404              d2.getAdminState().toString());
405          break;
406        case FIELD_DECOMMISSIONED:
407          ret = DFSUtil.DECOM_COMPARATOR.compare(d1, d2);
408          break;
409        case FIELD_NAME: 
410          ret = d1.getHostName().compareTo(d2.getHostName());
411          break;
412        default:
413          throw new IllegalArgumentException("Invalid sortField");
414        }
415        return (sortOrder == SORT_ORDER_DSC) ? -ret : ret;
416      }
417    }
418        
419    Collections.sort(nodes, new NodeComapare(field, order));
420  }
421
422  public static void printPathWithLinks(String dir, JspWriter out, 
423                                        int namenodeInfoPort,
424                                        String tokenString,
425                                        String nnAddress
426                                        ) throws IOException {
427    try {
428      String[] parts = dir.split(Path.SEPARATOR);
429      StringBuilder tempPath = new StringBuilder(dir.length());
430      out.print("<a href=\"browseDirectory.jsp" + "?dir="+ Path.SEPARATOR
431          + "&namenodeInfoPort=" + namenodeInfoPort
432          + getDelegationTokenUrlParam(tokenString) 
433          + getUrlParam(NAMENODE_ADDRESS, nnAddress) + "\">" + Path.SEPARATOR
434          + "</a>");
435      tempPath.append(Path.SEPARATOR);
436      for (int i = 0; i < parts.length-1; i++) {
437        if (!parts[i].equals("")) {
438          tempPath.append(parts[i]);
439          out.print("<a href=\"browseDirectory.jsp" + "?dir="
440              + HtmlQuoting.quoteHtmlChars(tempPath.toString()) + "&namenodeInfoPort=" + namenodeInfoPort
441              + getDelegationTokenUrlParam(tokenString)
442              + getUrlParam(NAMENODE_ADDRESS, nnAddress));
443          out.print("\">" + HtmlQuoting.quoteHtmlChars(parts[i]) + "</a>" + Path.SEPARATOR);
444          tempPath.append(Path.SEPARATOR);
445        }
446      }
447      if(parts.length > 0) {
448        out.print(HtmlQuoting.quoteHtmlChars(parts[parts.length-1]));
449      }
450    }
451    catch (UnsupportedEncodingException ex) {
452      ex.printStackTrace();
453    }
454  }
455
456  public static void printGotoForm(JspWriter out,
457                                   int namenodeInfoPort,
458                                   String tokenString,
459                                   String file,
460                                   String nnAddress) throws IOException {
461    out.print("<form action=\"browseDirectory.jsp\" method=\"get\" name=\"goto\">");
462    out.print("Goto : ");
463    out.print("<input name=\"dir\" type=\"text\" width=\"50\" id=\"dir\" value=\""+ HtmlQuoting.quoteHtmlChars(file)+"\"/>");
464    out.print("<input name=\"go\" type=\"submit\" value=\"go\"/>");
465    out.print("<input name=\"namenodeInfoPort\" type=\"hidden\" "
466        + "value=\"" + namenodeInfoPort  + "\"/>");
467    if (UserGroupInformation.isSecurityEnabled()) {
468      out.print("<input name=\"" + DELEGATION_PARAMETER_NAME
469          + "\" type=\"hidden\" value=\"" + tokenString + "\"/>");
470    }
471    out.print("<input name=\""+ NAMENODE_ADDRESS +"\" type=\"hidden\" "
472        + "value=\"" + nnAddress  + "\"/>");
473    out.print("</form>");
474  }
475  
476  public static void createTitle(JspWriter out, 
477                                 HttpServletRequest req, 
478                                 String  file) throws IOException{
479    if(file == null) file = "";
480    int start = Math.max(0,file.length() - 100);
481    if(start != 0)
482      file = "..." + file.substring(start, file.length());
483    out.print("<title>HDFS:" + file + "</title>");
484  }
485
486  /** Convert a String to chunk-size-to-view. */
487  public static int string2ChunkSizeToView(String s, int defaultValue) {
488    int n = s == null? 0: Integer.parseInt(s);
489    return n > 0? n: defaultValue;
490  }
491
492  /** Return a table containing version information. */
493  public static String getVersionTable() {
494    return "<div class='dfstable'><table>"       
495        + "\n  <tr><td class='col1'>Version:</td><td>" + VersionInfo.getVersion() + ", " + VersionInfo.getRevision() + "</td></tr>"
496        + "\n  <tr><td class='col1'>Compiled:</td><td>" + VersionInfo.getDate() + " by " + VersionInfo.getUser() + " from " + VersionInfo.getBranch() + "</td></tr>"
497        + "\n</table></div>";
498  }
499
500  /**
501   * Validate filename. 
502   * @return null if the filename is invalid.
503   *         Otherwise, return the validated filename.
504   */
505  public static String validatePath(String p) {
506    return p == null || p.length() == 0?
507        null: new Path(p).toUri().getPath();
508  }
509
510  /**
511   * Validate a long value. 
512   * @return null if the value is invalid.
513   *         Otherwise, return the validated Long object.
514   */
515  public static Long validateLong(String value) {
516    return value == null? null: Long.parseLong(value);
517  }
518
519  /**
520   * Validate a URL.
521   * @return null if the value is invalid.
522   *         Otherwise, return the validated URL String.
523   */
524  public static String validateURL(String value) {
525    try {
526      return URLEncoder.encode(new URL(value).toString(), "UTF-8");
527    } catch (IOException e) {
528      return null;
529    }
530  }
531  
532  /**
533   * If security is turned off, what is the default web user?
534   * @param conf the configuration to look in
535   * @return the remote user that was configuration
536   */
537  public static UserGroupInformation getDefaultWebUser(Configuration conf
538                                                       ) throws IOException {
539    return UserGroupInformation.createRemoteUser(getDefaultWebUserName(conf));
540  }
541
542  private static String getDefaultWebUserName(Configuration conf
543      ) throws IOException {
544    String user = conf.get(
545        HADOOP_HTTP_STATIC_USER, DEFAULT_HADOOP_HTTP_STATIC_USER);
546    if (user == null || user.length() == 0) {
547      throw new IOException("Cannot determine UGI from request or conf");
548    }
549    return user;
550  }
551
552  private static InetSocketAddress getNNServiceAddress(ServletContext context,
553      HttpServletRequest request) {
554    String namenodeAddressInUrl = request.getParameter(NAMENODE_ADDRESS);
555    InetSocketAddress namenodeAddress = null;
556    if (namenodeAddressInUrl != null) {
557      namenodeAddress = NetUtils.createSocketAddr(namenodeAddressInUrl);
558    } else if (context != null) {
559      namenodeAddress = NameNodeHttpServer.getNameNodeAddressFromContext(
560          context); 
561    }
562    if (namenodeAddress != null) {
563      return namenodeAddress;
564    }
565    return null;
566  }
567
568  /** Same as getUGI(null, request, conf). */
569  public static UserGroupInformation getUGI(HttpServletRequest request,
570      Configuration conf) throws IOException {
571    return getUGI(null, request, conf);
572  }
573  
574  /** Same as getUGI(context, request, conf, KERBEROS_SSL, true). */
575  public static UserGroupInformation getUGI(ServletContext context,
576      HttpServletRequest request, Configuration conf) throws IOException {
577    return getUGI(context, request, conf, AuthenticationMethod.KERBEROS_SSL, true);
578  }
579
580  /**
581   * Get {@link UserGroupInformation} and possibly the delegation token out of
582   * the request.
583   * @param context the ServletContext that is serving this request.
584   * @param request the http request
585   * @param conf configuration
586   * @param secureAuthMethod the AuthenticationMethod used in secure mode.
587   * @param tryUgiParameter Should it try the ugi parameter?
588   * @return a new user from the request
589   * @throws AccessControlException if the request has no token
590   */
591  public static UserGroupInformation getUGI(ServletContext context,
592      HttpServletRequest request, Configuration conf,
593      final AuthenticationMethod secureAuthMethod,
594      final boolean tryUgiParameter) throws IOException {
595    UserGroupInformation ugi = null;
596    final String usernameFromQuery = getUsernameFromQuery(request, tryUgiParameter);
597    final String doAsUserFromQuery = request.getParameter(DoAsParam.NAME);
598    final String remoteUser;
599   
600    if (UserGroupInformation.isSecurityEnabled()) {
601      remoteUser = request.getRemoteUser();
602      final String tokenString = request.getParameter(DELEGATION_PARAMETER_NAME);
603      if (tokenString != null) {
604        // Token-based connections need only verify the effective user, and
605        // disallow proxying to different user.  Proxy authorization checks
606        // are not required since the checks apply to issuing a token.
607        ugi = getTokenUGI(context, request, tokenString, conf);
608        checkUsername(ugi.getShortUserName(), usernameFromQuery);
609        checkUsername(ugi.getShortUserName(), doAsUserFromQuery);
610      } else if (remoteUser == null) {
611        throw new IOException(
612            "Security enabled but user not authenticated by filter");
613      }
614    } else {
615      // Security's not on, pull from url or use default web user
616      remoteUser = (usernameFromQuery == null)
617          ? getDefaultWebUserName(conf) // not specified in request
618          : usernameFromQuery;
619    }
620
621    if (ugi == null) { // security is off, or there's no token
622      ugi = UserGroupInformation.createRemoteUser(remoteUser);
623      checkUsername(ugi.getShortUserName(), usernameFromQuery);
624      if (UserGroupInformation.isSecurityEnabled()) {
625        // This is not necessarily true, could have been auth'ed by user-facing
626        // filter
627        ugi.setAuthenticationMethod(secureAuthMethod);
628      }
629      if (doAsUserFromQuery != null) {
630        // create and attempt to authorize a proxy user
631        ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, ugi);
632        ProxyUsers.authorize(ugi, getRemoteAddr(request));
633      }
634    }
635    
636    if(LOG.isDebugEnabled())
637      LOG.debug("getUGI is returning: " + ugi.getShortUserName());
638    return ugi;
639  }
640
641  private static UserGroupInformation getTokenUGI(ServletContext context,
642                                                  HttpServletRequest request,
643                                                  String tokenString,
644                                                  Configuration conf)
645                                                      throws IOException {
646    final Token<DelegationTokenIdentifier> token =
647        new Token<DelegationTokenIdentifier>();
648    token.decodeFromUrlString(tokenString);
649    InetSocketAddress serviceAddress = getNNServiceAddress(context, request);
650    if (serviceAddress != null) {
651      SecurityUtil.setTokenService(token, serviceAddress);
652      token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND);
653    }
654
655    ByteArrayInputStream buf =
656        new ByteArrayInputStream(token.getIdentifier());
657    DataInputStream in = new DataInputStream(buf);
658    DelegationTokenIdentifier id = new DelegationTokenIdentifier();
659    id.readFields(in);
660    if (context != null) {
661      final NameNode nn = NameNodeHttpServer.getNameNodeFromContext(context);
662      if (nn != null) {
663        // Verify the token.
664        nn.getNamesystem().verifyToken(id, token.getPassword());
665      }
666    }
667    UserGroupInformation ugi = id.getUser();
668    ugi.addToken(token);
669    return ugi;
670  }
671
672  // honor the X-Forwarded-For header set by a configured set of trusted
673  // proxy servers.  allows audit logging and proxy user checks to work
674  // via an http proxy
675  public static String getRemoteAddr(HttpServletRequest request) {
676    String remoteAddr = request.getRemoteAddr();
677    String proxyHeader = request.getHeader("X-Forwarded-For");
678    if (proxyHeader != null && ProxyServers.isProxyServer(remoteAddr)) {
679      final String clientAddr = proxyHeader.split(",")[0].trim();
680      if (!clientAddr.isEmpty()) {
681        remoteAddr = clientAddr;
682      }
683    }
684    return remoteAddr;
685  }
686
687
688  /**
689   * Expected user name should be a short name.
690   */
691  private static void checkUsername(final String expected, final String name
692      ) throws IOException {
693    if (expected == null && name != null) {
694      throw new IOException("Usernames not matched: expecting null but name="
695          + name);
696    }
697    if (name == null) { //name is optional, null is okay
698      return;
699    }
700    KerberosName u = new KerberosName(name);
701    String shortName = u.getShortName();
702    if (!shortName.equals(expected)) {
703      throw new IOException("Usernames not matched: name=" + shortName
704          + " != expected=" + expected);
705    }
706  }
707
708  private static String getUsernameFromQuery(final HttpServletRequest request,
709      final boolean tryUgiParameter) {
710    String username = request.getParameter(UserParam.NAME);
711    if (username == null && tryUgiParameter) {
712      //try ugi parameter
713      final String ugiStr = request.getParameter("ugi");
714      if (ugiStr != null) {
715        username = ugiStr.split(",")[0];
716      }
717    }
718    return username;
719  }
720
721  /**
722   * Returns the url parameter for the given token string.
723   * @param tokenString
724   * @return url parameter
725   */
726  public static String getDelegationTokenUrlParam(String tokenString) {
727    if (tokenString == null ) {
728      return "";
729    }
730    if (UserGroupInformation.isSecurityEnabled()) {
731      return SET_DELEGATION + tokenString;
732    } else {
733      return "";
734    }
735  }
736
737  /**
738   * Returns the url parameter for the given string, prefixed with
739   * paramSeparator.
740   * 
741   * @param name parameter name
742   * @param val parameter value
743   * @param paramSeparator URL parameter prefix, i.e. either '?' or '&'
744   * @return url parameter
745   */
746  public static String getUrlParam(String name, String val, String paramSeparator) {
747    return val == null ? "" : paramSeparator + name + "=" + val;
748  }
749  
750  /**
751   * Returns the url parameter for the given string, prefixed with '?' if
752   * firstParam is true, prefixed with '&' if firstParam is false.
753   * 
754   * @param name parameter name
755   * @param val parameter value
756   * @param firstParam true if this is the first parameter in the list, false otherwise
757   * @return url parameter
758   */
759  public static String getUrlParam(String name, String val, boolean firstParam) {
760    return getUrlParam(name, val, firstParam ? "?" : "&");
761  }
762  
763  /**
764   * Returns the url parameter for the given string, prefixed with '&'.
765   * 
766   * @param name parameter name
767   * @param val parameter value
768   * @return url parameter
769   */
770  public static String getUrlParam(String name, String val) {
771    return getUrlParam(name, val, false);
772  }
773}