Blame src/url.cc

Packit 8f70b4
/*
Packit 8f70b4
 * lftp - file transfer program
Packit 8f70b4
 *
Packit 8f70b4
 * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
Packit 8f70b4
 *
Packit 8f70b4
 * This program is free software; you can redistribute it and/or modify
Packit 8f70b4
 * it under the terms of the GNU General Public License as published by
Packit 8f70b4
 * the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
 * (at your option) any later version.
Packit 8f70b4
 *
Packit 8f70b4
 * This program is distributed in the hope that it will be useful,
Packit 8f70b4
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
 * GNU General Public License for more details.
Packit 8f70b4
 *
Packit 8f70b4
 * You should have received a copy of the GNU General Public License
Packit 8f70b4
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
#include "trio.h"
Packit 8f70b4
#include "xstring.h"
Packit 8f70b4
#include <ctype.h>
Packit 8f70b4
#include <assert.h>
Packit 8f70b4
#include "url.h"
Packit 8f70b4
#include "ascii_ctype.h"
Packit 8f70b4
#include "ConnectionSlot.h"
Packit 8f70b4
#include "bookmark.h"
Packit 8f70b4
#include "misc.h"
Packit 8f70b4
#include "log.h"
Packit 8f70b4
#include "network.h"
Packit 8f70b4
Packit 8f70b4
/*
Packit 8f70b4
   URL -> [PROTO://]CONNECT[[:]/PATH]
Packit 8f70b4
   CONNECT -> [USER[:PASS]@]HOST[:PORT]
Packit 8f70b4
Packit 8f70b4
   exceptions:
Packit 8f70b4
      file:/PATH
Packit 8f70b4
      bm:BOOKMARK[/PATH]
Packit 8f70b4
      slot:SLOT[/PATH]
Packit 8f70b4
*/
Packit 8f70b4
Packit 8f70b4
static bool valid_slot(const char *s);
Packit 8f70b4
static bool valid_bm(const char *s);
Packit 8f70b4
Packit 8f70b4
ParsedURL::ParsedURL(const char *url,bool proto_required,bool use_rfc1738)
Packit 8f70b4
{
Packit 8f70b4
   parse(url,proto_required,use_rfc1738);
Packit 8f70b4
}
Packit 8f70b4
ParsedURL::~ParsedURL()
Packit 8f70b4
{
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void ParsedURL::parse(const char *url,bool proto_required,bool use_rfc1738)
Packit 8f70b4
{
Packit 8f70b4
   orig_url.set(url);
Packit 8f70b4
   xstring_c connect;
Packit 8f70b4
   const char *base=url;
Packit 8f70b4
   const char *scan=base;
Packit 8f70b4
   while(is_ascii_alpha(*scan))
Packit 8f70b4
      scan++;
Packit 8f70b4
   if(scan[0]==':' && scan[1]=='/' && scan[2]=='/')
Packit 8f70b4
   {
Packit 8f70b4
      // found protocol
Packit 8f70b4
      proto.nset(base,scan-base);
Packit 8f70b4
      base=scan+=3;
Packit 8f70b4
      if(!strcmp(proto,"file") && scan[0]=='/')
Packit 8f70b4
	 goto file_with_no_host;
Packit 8f70b4
   }
Packit 8f70b4
   else if(scan[0]==':' && !strncmp(base,"file:",5))
Packit 8f70b4
   {
Packit 8f70b4
      // special form for file protocol
Packit 8f70b4
      proto.nset(base,scan-base);
Packit 8f70b4
      scan++;
Packit 8f70b4
   file_with_no_host:
Packit 8f70b4
      path.set(scan);
Packit 8f70b4
      host.set("localhost");
Packit 8f70b4
      goto decode;
Packit 8f70b4
   }
Packit 8f70b4
   else if(scan[0]==':'
Packit 8f70b4
   && ((!strncmp(base,"slot:",5) && valid_slot(scan+1))
Packit 8f70b4
       || (!strncmp(base,"bm:",3) && valid_bm(scan+1))))
Packit 8f70b4
   {
Packit 8f70b4
      // special form for selecting a connection slot or a bookmark
Packit 8f70b4
      proto.nset(base,scan-base);
Packit 8f70b4
      scan++;
Packit 8f70b4
      base=scan;
Packit 8f70b4
      scan=strchr(scan,'/');
Packit 8f70b4
      if(scan)
Packit 8f70b4
      {
Packit 8f70b4
	 host.nset(base,scan-base);
Packit 8f70b4
	 path.set(scan);
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 host.set(base);
Packit 8f70b4
      goto decode;
Packit 8f70b4
   }
Packit 8f70b4
   else if(proto_required)
Packit 8f70b4
   {
Packit 8f70b4
      // all the rest is path, if protocol is required.
Packit 8f70b4
      path.set(base);
Packit 8f70b4
      goto decode;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   scan=base;
Packit 8f70b4
   while(*scan && *scan!='/')
Packit 8f70b4
      scan++; // skip host name, port and user:pass
Packit 8f70b4
Packit 8f70b4
   connect.nset(base,scan-base-(scan>base && scan[-1]==':'));
Packit 8f70b4
Packit 8f70b4
   if(*scan=='/') // directory
Packit 8f70b4
   {
Packit 8f70b4
      if(scan[1]=='~')
Packit 8f70b4
	 path.set(scan+1);
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 if((!xstrcmp(proto,"ftp") || !xstrcmp(proto,"hftp"))
Packit 8f70b4
	 && use_rfc1738)
Packit 8f70b4
	 {
Packit 8f70b4
	    // special handling for ftp protocol.
Packit 8f70b4
	    if(!strncasecmp(scan+1,"%2F",3))
Packit 8f70b4
	       path.set(scan+1);
Packit 8f70b4
	    else if(!(is_ascii_alpha(scan[1]) && scan[2]==':' && scan[3]=='/'))
Packit 8f70b4
	       path.vset("~",scan,NULL);
Packit 8f70b4
	 }
Packit 8f70b4
	 else
Packit 8f70b4
	    path.set(scan);
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   else if(proto)
Packit 8f70b4
   {
Packit 8f70b4
      if(!strcmp(proto,"http") || !strcmp(proto,"https"))
Packit 8f70b4
	 path.set("/");
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   // try to extract user name/password
Packit 8f70b4
   base=connect;
Packit 8f70b4
   scan=strrchr(base,'@');
Packit 8f70b4
   if(scan)
Packit 8f70b4
   {
Packit 8f70b4
      user.nset(base,scan-base);
Packit 8f70b4
      base=scan+1;
Packit 8f70b4
      scan=user;
Packit 8f70b4
      while(*scan && *scan!=':')
Packit 8f70b4
	 scan++;
Packit 8f70b4
      if(*scan==':')
Packit 8f70b4
      {
Packit 8f70b4
	 pass.set(scan+1);
Packit 8f70b4
	 user.truncate(scan-user);
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   // extract host name
Packit 8f70b4
   scan=base;
Packit 8f70b4
   if(*scan=='[') // RFC2732 [ipv6]
Packit 8f70b4
   {
Packit 8f70b4
      while(*scan && *scan!=']')
Packit 8f70b4
	 scan++;
Packit 8f70b4
      if(*scan==']')
Packit 8f70b4
      {
Packit 8f70b4
	 scan++;
Packit 8f70b4
	 host.nset(base+1,scan-base-2);
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 scan=base;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(scan==base)
Packit 8f70b4
   {
Packit 8f70b4
      while(*scan && *scan!=':')
Packit 8f70b4
	 scan++;
Packit 8f70b4
      host.nset(base,scan-base);
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(*scan==':') // port found
Packit 8f70b4
   {
Packit 8f70b4
      if(strchr(scan+1,':')==0)
Packit 8f70b4
      {
Packit 8f70b4
	 port.set(scan+1);
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 /* more than one colon - maybe it is ipv6 digital address */
Packit 8f70b4
	 host.set(base);
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
decode:
Packit 8f70b4
   user.url_decode();
Packit 8f70b4
   pass.url_decode();
Packit 8f70b4
   host.url_decode();
Packit 8f70b4
   path.url_decode();
Packit 8f70b4
Packit 8f70b4
   if(!xstrcmp(proto,"slot"))
Packit 8f70b4
   {
Packit 8f70b4
      const FileAccess *fa=ConnectionSlot::FindSession(host);
Packit 8f70b4
      if(!fa)
Packit 8f70b4
	 return;
Packit 8f70b4
      orig_url.set(0);
Packit 8f70b4
Packit 8f70b4
      proto.set(fa->GetProto());
Packit 8f70b4
      user.set(fa->GetUser());
Packit 8f70b4
      pass.set(fa->GetPassword());
Packit 8f70b4
      host.set(fa->GetHostName());
Packit 8f70b4
      port.set(fa->GetPort());
Packit 8f70b4
Packit 8f70b4
      FA::Path cwd(fa->GetCwd());
Packit 8f70b4
      if(path)
Packit 8f70b4
	 cwd.Change(path+(path[0]=='/'));
Packit 8f70b4
      path.set(cwd);
Packit 8f70b4
   }
Packit 8f70b4
   else if(!xstrcmp(proto,"bm"))
Packit 8f70b4
   {
Packit 8f70b4
      const char *bm=lftp_bookmarks.Lookup(host);
Packit 8f70b4
      if(!bm || !bm[0])
Packit 8f70b4
	 return;
Packit 8f70b4
      const char *new_url=0;
Packit 8f70b4
      xstring u(bm);
Packit 8f70b4
      if(orig_url)
Packit 8f70b4
      {
Packit 8f70b4
	 const char *new_path=orig_url+url::path_index(orig_url);
Packit 8f70b4
	 if(new_path[0]=='/')
Packit 8f70b4
	    new_path++;
Packit 8f70b4
	 if(new_path[0]=='/' || new_path[0]=='~')
Packit 8f70b4
	    u.truncate(url::path_index(u));
Packit 8f70b4
	 assert(u[0]);
Packit 8f70b4
	 if(u.last_char()!='/' && new_path[0]!='/')
Packit 8f70b4
	    u.append('/');
Packit 8f70b4
	 else if(u.last_char()=='/' && new_path[0]=='/')
Packit 8f70b4
	    new_path++;
Packit 8f70b4
	 u.append(new_path);
Packit 8f70b4
	 new_url=u;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 new_url=url_file(bm,path+(path && path[0]=='/'));
Packit 8f70b4
      parse(new_url,proto_required,use_rfc1738);
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static bool valid_slot(const char *cs)
Packit 8f70b4
{
Packit 8f70b4
   xstring& s=xstring::get_tmp(cs);
Packit 8f70b4
   s.truncate_at('/');
Packit 8f70b4
   s.url_decode();
Packit 8f70b4
   return 0!=ConnectionSlot::Find(s);
Packit 8f70b4
}
Packit 8f70b4
static bool valid_bm(const char *bm)
Packit 8f70b4
{
Packit 8f70b4
   xstring& s=xstring::get_tmp(bm);
Packit 8f70b4
   s.truncate_at('/');
Packit 8f70b4
   s.url_decode();
Packit 8f70b4
   const char *url=lftp_bookmarks.Lookup(s);
Packit 8f70b4
   return(url && !strchr(url,' ') && !strchr(url,'\t'));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int url::path_index(const char *base)
Packit 8f70b4
{
Packit 8f70b4
   const char *scan=base;
Packit 8f70b4
   while(is_ascii_alpha(*scan))
Packit 8f70b4
      scan++;
Packit 8f70b4
   if(scan[0]!=':')
Packit 8f70b4
      return 0;
Packit 8f70b4
   if(scan[1]=='/' && scan[2]=='/')
Packit 8f70b4
   {
Packit 8f70b4
      // found protocol
Packit 8f70b4
      const char *slash=strchr(scan+3,'/');
Packit 8f70b4
      if(slash)
Packit 8f70b4
	 return slash-base;
Packit 8f70b4
      return strlen(base);
Packit 8f70b4
   }
Packit 8f70b4
   else if(!strncmp(base,"file:",5))
Packit 8f70b4
   {
Packit 8f70b4
      // special form for file protocol
Packit 8f70b4
      return scan+1-base;
Packit 8f70b4
   }
Packit 8f70b4
   else if((!strncmp(base,"slot:",5) && valid_slot(base+5))
Packit 8f70b4
	|| (!strncmp(base,"bm:",3) && valid_bm(base+3)))
Packit 8f70b4
   {
Packit 8f70b4
      const char *slash=strchr(scan+1,'/');
Packit 8f70b4
      if(slash)
Packit 8f70b4
	 return slash-base;
Packit 8f70b4
      return strlen(base);
Packit 8f70b4
   }
Packit 8f70b4
   return 0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *url::path_ptr(const char *base)
Packit 8f70b4
{
Packit 8f70b4
   if(!base)
Packit 8f70b4
      return 0;
Packit 8f70b4
   return base+path_index(base);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
char *ParsedURL::Combine(const char *home,bool use_rfc1738)
Packit 8f70b4
{
Packit 8f70b4
   xstring buf("");
Packit 8f70b4
   return CombineTo(buf,home,use_rfc1738).borrow();
Packit 8f70b4
}
Packit 8f70b4
xstring& ParsedURL::CombineTo(xstring& u,const char *home,bool use_rfc1738) const
Packit 8f70b4
{
Packit 8f70b4
   bool is_file=!xstrcmp(proto,"file");
Packit 8f70b4
   bool is_ftp=(!xstrcmp(proto,"ftp") || !xstrcmp(proto,"hftp"));
Packit 8f70b4
Packit 8f70b4
   if(proto)
Packit 8f70b4
   {
Packit 8f70b4
      u.append(proto);
Packit 8f70b4
      u.append(is_file?":":"://");
Packit 8f70b4
   }
Packit 8f70b4
   if(user && !is_file)
Packit 8f70b4
   {
Packit 8f70b4
      u.append(url::encode(user,URL_USER_UNSAFE));
Packit 8f70b4
      if(pass)
Packit 8f70b4
      {
Packit 8f70b4
	 u.append(':');
Packit 8f70b4
	 u.append(url::encode(pass,URL_PASS_UNSAFE));
Packit 8f70b4
      }
Packit 8f70b4
      u.append('@');
Packit 8f70b4
   }
Packit 8f70b4
   if(host && !is_file) {
Packit 8f70b4
      unsigned encode_flags=0;
Packit 8f70b4
      if(xtld_name_ok(host))
Packit 8f70b4
	 encode_flags|=URL_ALLOW_8BIT;
Packit 8f70b4
      if(is_ipv6_address(host))
Packit 8f70b4
	 u.append('[').append(host).append(']');
Packit 8f70b4
      else
Packit 8f70b4
	 u.append_url_encoded(host,URL_HOST_UNSAFE,encode_flags);
Packit 8f70b4
   }
Packit 8f70b4
   if(port && !is_file)
Packit 8f70b4
   {
Packit 8f70b4
      u.append(':');
Packit 8f70b4
      u.append(url::encode(port,URL_PORT_UNSAFE));
Packit 8f70b4
   }
Packit 8f70b4
   if(path && strcmp(path,"~"))
Packit 8f70b4
   {
Packit 8f70b4
      if(path[0]!='/' && !is_file) // e.g. ~/path
Packit 8f70b4
	 u.append('/');
Packit 8f70b4
      int p_offset=0;
Packit 8f70b4
      if(is_ftp && use_rfc1738)
Packit 8f70b4
      {
Packit 8f70b4
	 // some cruft for ftp urls...
Packit 8f70b4
	 if(path[0]=='/' && xstrcmp(home,"/"))
Packit 8f70b4
	 {
Packit 8f70b4
	    u.append("/%2F");
Packit 8f70b4
	    p_offset=1;
Packit 8f70b4
	 }
Packit 8f70b4
	 else if(path[0]=='~' && path[1]=='/')
Packit 8f70b4
	    p_offset=2;
Packit 8f70b4
      }
Packit 8f70b4
      u.append(url::encode(path+p_offset,URL_PATH_UNSAFE));
Packit 8f70b4
   }
Packit 8f70b4
   return u;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const xstring& url::decode(const char *p)
Packit 8f70b4
{
Packit 8f70b4
   if(!p)
Packit 8f70b4
      return xstring::null;
Packit 8f70b4
   return xstring::get_tmp(p).url_decode();
Packit 8f70b4
}
Packit 8f70b4
const xstring& url::encode(const char *s,int len,const char *unsafe,unsigned flags)
Packit 8f70b4
{
Packit 8f70b4
   if(!s)
Packit 8f70b4
      return xstring::null;
Packit 8f70b4
   return xstring::get_tmp("").append_url_encoded(s,len,unsafe,flags);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
bool url::dir_needs_trailing_slash(const char *proto_c)
Packit 8f70b4
{
Packit 8f70b4
   if(!proto_c)
Packit 8f70b4
      return false;
Packit 8f70b4
   char *proto=alloca_strdup(proto_c);
Packit 8f70b4
   char *colon=strchr(proto,':');
Packit 8f70b4
   if(colon)
Packit 8f70b4
      *colon=0;
Packit 8f70b4
   return !strcasecmp(proto,"http")
Packit 8f70b4
       || !strcasecmp(proto,"https");
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
bool url::find_password_pos(const char *url,int *start,int *len)
Packit 8f70b4
{
Packit 8f70b4
   *start=*len=0;
Packit 8f70b4
Packit 8f70b4
   const char *scan=strstr(url,"://");
Packit 8f70b4
   if(!scan)
Packit 8f70b4
      return false;
Packit 8f70b4
Packit 8f70b4
   scan+=3;
Packit 8f70b4
Packit 8f70b4
   const char *at=strchr(scan,'@');
Packit 8f70b4
   if(!at)
Packit 8f70b4
      return false;
Packit 8f70b4
Packit 8f70b4
   const char *colon=strchr(scan,':');
Packit 8f70b4
   if(!colon || colon>at)
Packit 8f70b4
      return false;
Packit 8f70b4
Packit 8f70b4
   const char *slash=strchr(scan,'/');
Packit 8f70b4
   if(slash && slash
Packit 8f70b4
      return false;
Packit 8f70b4
Packit 8f70b4
   *start=colon+1-url;
Packit 8f70b4
   *len=at-colon-1;
Packit 8f70b4
   return true;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *url::hide_password(const char *url)
Packit 8f70b4
{
Packit 8f70b4
   int start,len;
Packit 8f70b4
   if(!find_password_pos(url,&start,&len))
Packit 8f70b4
      return url;
Packit 8f70b4
   return xstring::format("%.*sXXXX%s",start,url,url+start+len);
Packit 8f70b4
}
Packit 8f70b4
const char *url::remove_password(const char *url)
Packit 8f70b4
{
Packit 8f70b4
   int start,len;
Packit 8f70b4
   if(!find_password_pos(url,&start,&len))
Packit 8f70b4
      return url;
Packit 8f70b4
   return xstring::format("%.*s%s",start-1,url,url+start+len);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
bool url::is_url(const char *p)
Packit 8f70b4
{
Packit 8f70b4
   ParsedURL url(p,true);
Packit 8f70b4
   return url.proto!=0;
Packit 8f70b4
}