Example #1
0
uerr_t
retrieve_url (struct url * orig_parsed, const char *origurl, char **file,
              char **newloc, const char *refurl, int *dt, bool recursive,
              struct iri *iri, bool register_status)
{
  uerr_t result;
  char *url;
  bool location_changed;
  bool iri_fallbacked = 0;
  int dummy;
  char *mynewloc, *proxy;
  struct url *u = orig_parsed, *proxy_url;
  int up_error_code;            /* url parse error code */
  char *local_file;
  int redirection_count = 0;

  bool post_data_suspended = false;
  char *saved_post_data = NULL;
  char *saved_post_file_name = NULL;

  /* If dt is NULL, use local storage.  */
  if (!dt)
    {
      dt = &dummy;
      dummy = 0;
    }
  url = xstrdup (origurl);
  if (newloc)
    *newloc = NULL;
  if (file)
    *file = NULL;

  if (!refurl)
    refurl = opt.referer;

 redirected:
  /* (also for IRI fallbacking) */

  result = NOCONERROR;
  mynewloc = NULL;
  local_file = NULL;
  proxy_url = NULL;

  proxy = getproxy (u);
  if (proxy)
    {
      struct iri *pi = iri_new ();
      set_uri_encoding (pi, opt.locale, true);
      pi->utf8_encode = false;

      /* Parse the proxy URL.  */
      proxy_url = url_parse (proxy, &up_error_code, NULL, true);
      if (!proxy_url)
        {
          char *error = url_error (proxy, up_error_code);
          logprintf (LOG_NOTQUIET, _("Error parsing proxy URL %s: %s.\n"),
                     proxy, error);
          xfree (url);
          xfree (error);
          RESTORE_POST_DATA;
          result = PROXERR;
          goto bail;
        }
      if (proxy_url->scheme != SCHEME_HTTP && proxy_url->scheme != u->scheme)
        {
          logprintf (LOG_NOTQUIET, _("Error in proxy URL %s: Must be HTTP.\n"), proxy);
          url_free (proxy_url);
          xfree (url);
          RESTORE_POST_DATA;
          result = PROXERR;
          goto bail;
        }
    }

  if (u->scheme == SCHEME_HTTP
#ifdef HAVE_SSL
      || u->scheme == SCHEME_HTTPS
#endif
      || (proxy_url && proxy_url->scheme == SCHEME_HTTP))
    {
      result = http_loop (u, orig_parsed, &mynewloc, &local_file, refurl, dt,
                          proxy_url, iri);
    }
  else if (u->scheme == SCHEME_FTP)
    {
      /* If this is a redirection, temporarily turn off opt.ftp_glob
         and opt.recursive, both being undesirable when following
         redirects.  */
      bool oldrec = recursive, glob = opt.ftp_glob;
      if (redirection_count)
        oldrec = glob = false;

      result = ftp_loop (u, &local_file, dt, proxy_url, recursive, glob);
      recursive = oldrec;

      /* There is a possibility of having HTTP being redirected to
         FTP.  In these cases we must decide whether the text is HTML
         according to the suffix.  The HTML suffixes are `.html',
         `.htm' and a few others, case-insensitive.  */
      if (redirection_count && local_file && u->scheme == SCHEME_FTP)
        {
          if (has_html_suffix_p (local_file))
            *dt |= TEXTHTML;
        }
    }

  if (proxy_url)
    {
      url_free (proxy_url);
      proxy_url = NULL;
    }

  location_changed = (result == NEWLOCATION || result == NEWLOCATION_KEEP_POST);
  if (location_changed)
    {
      char *construced_newloc;
      struct url *newloc_parsed;

      assert (mynewloc != NULL);

      if (local_file)
        xfree (local_file);

      /* The HTTP specs only allow absolute URLs to appear in
         redirects, but a ton of boneheaded webservers and CGIs out
         there break the rules and use relative URLs, and popular
         browsers are lenient about this, so wget should be too. */
      construced_newloc = uri_merge (url, mynewloc);
      xfree (mynewloc);
      mynewloc = construced_newloc;

      /* Reset UTF-8 encoding state, keep the URI encoding and reset
         the content encoding. */
      iri->utf8_encode = opt.enable_iri;
      set_content_encoding (iri, NULL);
      xfree_null (iri->orig_url);
      iri->orig_url = NULL;

      /* Now, see if this new location makes sense. */
      newloc_parsed = url_parse (mynewloc, &up_error_code, iri, true);
      if (!newloc_parsed)
        {
          char *error = url_error (mynewloc, up_error_code);
          logprintf (LOG_NOTQUIET, "%s: %s.\n", escnonprint_uri (mynewloc),
                     error);
          if (orig_parsed != u)
            {
              url_free (u);
            }
          xfree (url);
          xfree (mynewloc);
          xfree (error);
          RESTORE_POST_DATA;
          goto bail;
        }

      /* Now mynewloc will become newloc_parsed->url, because if the
         Location contained relative paths like .././something, we
         don't want that propagating as url.  */
      xfree (mynewloc);
      mynewloc = xstrdup (newloc_parsed->url);

      /* Check for max. number of redirections.  */
      if (++redirection_count > opt.max_redirect)
        {
          logprintf (LOG_NOTQUIET, _("%d redirections exceeded.\n"),
                     opt.max_redirect);
          url_free (newloc_parsed);
          if (orig_parsed != u)
            {
              url_free (u);
            }
          xfree (url);
          xfree (mynewloc);
          RESTORE_POST_DATA;
          result = WRONGCODE;
          goto bail;
        }

      xfree (url);
      url = mynewloc;
      if (orig_parsed != u)
        {
          url_free (u);
        }
      u = newloc_parsed;

      /* If we're being redirected from POST, and we received a
         redirect code different than 307, we don't want to POST
         again.  Many requests answer POST with a redirection to an
         index page; that redirection is clearly a GET.  We "suspend"
         POST data for the duration of the redirections, and restore
         it when we're done.
	 
	 RFC2616 HTTP/1.1 introduces code 307 Temporary Redirect
	 specifically to preserve the method of the request.
	 */
      if (result != NEWLOCATION_KEEP_POST && !post_data_suspended)
        SUSPEND_POST_DATA;

      goto redirected;
    }

  /* Try to not encode in UTF-8 if fetching failed */
  if (!(*dt & RETROKF) && iri->utf8_encode)
    {
      iri->utf8_encode = false;
      if (orig_parsed != u)
        {
          url_free (u);
        }
      u = url_parse (origurl, NULL, iri, true);
      if (u)
        {
          DEBUGP (("[IRI fallbacking to non-utf8 for %s\n", quote (url)));
          url = xstrdup (u->url);
          iri_fallbacked = 1;
          goto redirected;
        }
      else
          DEBUGP (("[Couldn't fallback to non-utf8 for %s\n", quote (url)));
    }

  if (local_file && u && *dt & RETROKF)
    {
      register_download (u->url, local_file);

      if (!opt.spider && redirection_count && 0 != strcmp (origurl, u->url))
        register_redirection (origurl, u->url);

      if (*dt & TEXTHTML)
        register_html (local_file);

      if (*dt & TEXTCSS)
        register_css (local_file);
    }

  if (file)
    *file = local_file ? local_file : NULL;
  else
    xfree_null (local_file);

  if (orig_parsed != u)
    {
      url_free (u);
    }

  if (redirection_count || iri_fallbacked)
    {
      if (newloc)
        *newloc = url;
      else
        xfree (url);
    }
  else
    {
      if (newloc)
        *newloc = NULL;
      xfree (url);
    }

  RESTORE_POST_DATA;

bail:
  if (register_status)
    inform_exit_status (result);
  return result;
}
Example #2
0
void test_install( char *package, char *gisbase, char *pkg_short_name, int pkg_major, int pkg_minor, int pkg_revision, char *grass_version )
{
  char tmp[2048];
  char dir[2048];
  char sysstr[2048];
  int error = stat( gisbase, &buf.st_dev );
  struct stat buf;
  FILE *f;
  char *verstr;
  char *grass_major;
  char *grass_minor;
  char *grass_revision;
  int major, minor, revision;
  if ( error < 0 )
  {
    print_error( -5, "installation directory invalid: %s\n", strerror( *(int*)(__errno_location( )) ) );
  }
  sprintf( GINSTALL_DST, "GINSTALL_DST=%s", gisbase );
  putenv( GINSTALL_DST );
  sprintf( tmp, "%s/include", gisbase );
  sprintf( GINSTALL_INC, "GINSTALL_INC=%s", tmp );
  putenv( GINSTALL_INC );
  sprintf( tmp, "%s/lib", gisbase );
  sprintf( GINSTALL_LIB, "GINSTALL_LIB=%s", tmp );
  putenv( GINSTALL_LIB );
  sprintf( GEM_GRASS_DIR, "GEM_GRASS_DIR=%s", gisbase );
  putenv( GEM_GRASS_DIR );
  verstr = strdup( grass_version );
  grass_major = strtok( verstr, "." );
  grass_minor = strtok( 0, "." );
  grass_revision = strtok( 0, "." );
  major = strtol( grass_major, 0, 10 );
  minor = strtol( grass_minor, 0, 10 );
  revision = strtol( grass_revision, 0, 10 );
  free( verstr );
  atexit( &exit_tmp );
  sprintf( dir, "%s/src", basename( package ) );
  error = chdir( dir );
  if ( error < 0 )
  {
    print_error( -2, "extension files in '%s' not accessible: %s\n", package, strerror( *(int*)(__errno_location( )) ) );
  }
  if ( SKIP_CFG == 0 )
  {
    if ( VERBOSE )
    {
      fwrite( "Running configure script:\n", 1, 26, stdout );
      sprintf( sysstr, "sh %s %s", CONFIG_CMD, CONFIG_OPTS );
      error = system( sysstr );
    }
    else
    {
      fwrite( "Configuring...", 1, 14, stdout );
      sprintf( sysstr, "sh %s %s --quiet &&gt; %s", CONFIG_CMD, CONFIG_OPTS, TMP_NULL );
      error = system( sysstr );
    }
    if ( error == -1 )
      print_error( -27, "could not run configure script.\n" );
    if ( error > 0 )
      print_error( -3, "system configuration failed.\n" );
    print_done( );
    print_cfg( );
  }
  sprintf( GEM_EXT_NAME, "GEM_EXT_NAME=%s", pkg_short_name );
  putenv( GEM_EXT_NAME );
  sprintf( tmp, "%i.%i.%i", pkg_major, pkg_minor, pkg_revision );
  sprintf( GEM_EXT_VERSION, "GEM_EXT_VERSION=%s", tmp );
  putenv( GEM_EXT_VERSION );
  dump_plain( "../description", TMP_DESCR );
  dump_plain( "../info", TMP_INFO );
  dump_plain( "../depends", TMP_DEPS );
  dump_plain( "../bugs", TMP_BUGS );
  dump_plain( "../authors", TMP_AUTHORS );
  sprintf( GEM_EXT_DESCR, "GEM_EXT_DESCR=%s", TMP_DESCR );
  putenv( GEM_EXT_DESCR );
  sprintf( GEM_EXT_INFO, "GEM_EXT_INFO=%s", TMP_INFO );
  putenv( GEM_EXT_INFO );
  sprintf( GEM_EXT_DEPS, "GEM_EXT_DEPS=%s", TMP_DEPS );
  putenv( GEM_EXT_DEPS );
  sprintf( GEM_EXT_BUGS, "GEM_EXT_BUGS=%s", TMP_BUGS );
  putenv( GEM_EXT_BUGS );
  sprintf( GEM_EXT_AUTHORS, "GEM_EXT_AUTHORS=%s", TMP_AUTHORS );
  putenv( GEM_EXT_AUTHORS );
  atexit( &exit_tmp );
  check_dependencies( package, gisbase, grass_version );
  if ( VERBOSE )
  {
    fprintf( stdout, "Running '%s':\n", MAKE_CMD );
    sprintf( sysstr, "%s -f Makefile", MAKE_CMD );
    error = system( sysstr );
  }
  else
  {
    fwrite( "Compiling...", 1, 12, stdout );
    sprintf( sysstr, "%s -f Makefile &&gt; %s", MAKE_CMD, TMP_NULL );
    error = system( sysstr );
  }
  if ( error == -1 && VERBOSE == 0 )
    print_error( -9, "could not run '%s' do you have make tools installed?\n", MAKE_CMD[0] );
  if ( error > 0 )
    print_error( -4, "source code could not be compiled.\n \t\t\tRun again with option -v to see what is causing trouble.\n" );
  print_done( );
  fwrite( "Installing...", 1, 13, stdout );
  f = (FILE*)fopen( "../uninstall", "r" );
  if ( f == 0 )
  {
    print_warning( "error checking for uninstall script: %s\n \t\t\t\tUninstalling this extension may leave orphaned files on your system", strerror( *(int*)(__errno_location( )) ) );
  }
  else
    fclose( f );
  register_extension( gisbase, "src", pkg_short_name, pkg_major, pkg_minor, pkg_revision );
  check_dependencies( package, gisbase, grass_version );
  if ( major == 6 && minor <= 0 )
    register_entries_gisman( pkg_short_name, gisbase );
  register_entries_gisman2( pkg_short_name, gisbase );
  register_html( pkg_short_name, gisbase, pkg_major, pkg_minor, pkg_revision );
  fprintf( stdout, "(skipping '%s install')...", MAKE_CMD );
  print_done( );
  return;
}
Example #3
0
uerr_t
retrieve_url (const char *origurl, char **file, char **newloc,
              const char *refurl, int *dt, bool recursive)
{
  uerr_t result;
  char *url;
  bool location_changed;
  int dummy;
  char *mynewloc, *proxy;
  struct url *u, *proxy_url;
  int up_error_code;            /* url parse error code */
  char *local_file;
  int redirection_count = 0;

  bool post_data_suspended = false;
  char *saved_post_data = NULL;
  char *saved_post_file_name = NULL;

  /* If dt is NULL, use local storage.  */
  if (!dt)
    {
      dt = &dummy;
      dummy = 0;
    }
  url = xstrdup (origurl);
  if (newloc)
    *newloc = NULL;
  if (file)
    *file = NULL;

  u = url_parse (url, &up_error_code);
  if (!u)
    {
      logprintf (LOG_NOTQUIET, "%s: %s.\n", url, url_error (up_error_code));
      xfree (url);
      return URLERROR;
    }

  if (!refurl)
    refurl = opt.referer;

 redirected:

  result = NOCONERROR;
  mynewloc = NULL;
  local_file = NULL;
  proxy_url = NULL;

  proxy = getproxy (u);
  if (proxy)
    {
      /* Parse the proxy URL.  */
      proxy_url = url_parse (proxy, &up_error_code);
      if (!proxy_url)
        {
          logprintf (LOG_NOTQUIET, _("Error parsing proxy URL %s: %s.\n"),
                     proxy, url_error (up_error_code));
          xfree (url);
          RESTORE_POST_DATA;
          return PROXERR;
        }
      if (proxy_url->scheme != SCHEME_HTTP && proxy_url->scheme != u->scheme)
        {
          logprintf (LOG_NOTQUIET, _("Error in proxy URL %s: Must be HTTP.\n"), proxy);
          url_free (proxy_url);
          xfree (url);
          RESTORE_POST_DATA;
          return PROXERR;
        }
    }

  if (u->scheme == SCHEME_HTTP
#ifdef HAVE_SSL
      || u->scheme == SCHEME_HTTPS
#endif
      || (proxy_url && proxy_url->scheme == SCHEME_HTTP))
    {
      result = http_loop (u, &mynewloc, &local_file, refurl, dt, proxy_url);
    }
  else if (u->scheme == SCHEME_FTP)
    {
      /* If this is a redirection, temporarily turn off opt.ftp_glob
         and opt.recursive, both being undesirable when following
         redirects.  */
      bool oldrec = recursive, glob = opt.ftp_glob;
      if (redirection_count)
        oldrec = glob = false;

      result = ftp_loop (u, dt, proxy_url, recursive, glob);
      recursive = oldrec;

      /* There is a possibility of having HTTP being redirected to
         FTP.  In these cases we must decide whether the text is HTML
         according to the suffix.  The HTML suffixes are `.html',
         `.htm' and a few others, case-insensitive.  */
      if (redirection_count && local_file && u->scheme == SCHEME_FTP)
        {
          if (has_html_suffix_p (local_file))
            *dt |= TEXTHTML;
        }
    }

  if (proxy_url)
    {
      url_free (proxy_url);
      proxy_url = NULL;
    }

  location_changed = (result == NEWLOCATION);
  if (location_changed)
    {
      char *construced_newloc;
      struct url *newloc_parsed;

      assert (mynewloc != NULL);

      if (local_file)
        xfree (local_file);

      /* The HTTP specs only allow absolute URLs to appear in
         redirects, but a ton of boneheaded webservers and CGIs out
         there break the rules and use relative URLs, and popular
         browsers are lenient about this, so wget should be too. */
      construced_newloc = uri_merge (url, mynewloc);
      xfree (mynewloc);
      mynewloc = construced_newloc;

      /* Now, see if this new location makes sense. */
      newloc_parsed = url_parse (mynewloc, &up_error_code);
      if (!newloc_parsed)
        {
          logprintf (LOG_NOTQUIET, "%s: %s.\n", escnonprint_uri (mynewloc),
                     url_error (up_error_code));
          url_free (u);
          xfree (url);
          xfree (mynewloc);
          RESTORE_POST_DATA;
          return result;
        }

      /* Now mynewloc will become newloc_parsed->url, because if the
         Location contained relative paths like .././something, we
         don't want that propagating as url.  */
      xfree (mynewloc);
      mynewloc = xstrdup (newloc_parsed->url);

      /* Check for max. number of redirections.  */
      if (++redirection_count > opt.max_redirect)
        {
          logprintf (LOG_NOTQUIET, _("%d redirections exceeded.\n"),
                     opt.max_redirect);
          url_free (newloc_parsed);
          url_free (u);
          xfree (url);
          xfree (mynewloc);
          RESTORE_POST_DATA;
          return WRONGCODE;
        }

      xfree (url);
      url = mynewloc;
      url_free (u);
      u = newloc_parsed;

      /* If we're being redirected from POST, we don't want to POST
         again.  Many requests answer POST with a redirection to an
         index page; that redirection is clearly a GET.  We "suspend"
         POST data for the duration of the redirections, and restore
         it when we're done. */
      if (!post_data_suspended)
        SUSPEND_POST_DATA;

      goto redirected;
    }

  if (local_file)
    {
      if (*dt & RETROKF)
        {
          register_download (u->url, local_file);
          if (redirection_count && 0 != strcmp (origurl, u->url))
            register_redirection (origurl, u->url);
          if (*dt & TEXTHTML)
            register_html (u->url, local_file);
        }
    }

  if (file)
    *file = local_file ? local_file : NULL;
  else
    xfree_null (local_file);

  url_free (u);

  if (redirection_count)
    {
      if (newloc)
        *newloc = url;
      else
        xfree (url);
    }
  else
    {
      if (newloc)
        *newloc = NULL;
      xfree (url);
    }

  RESTORE_POST_DATA;

  return result;
}
Example #4
0
void bin_install( char *package, char *gisbase, char *bins, char *pkg_short_name, int pkg_major, int pkg_minor, int pkg_revision, char *grass_version )
{
  char tmp[2048];
  char dir[2048];
  char install_cmd[2048];
  char post_cmd[2048];
  int error = stat( gisbase, &buf.st_dev );
  struct stat buf;
  FILE *f;
  char *verstr;
  char *grass_major;
  char *grass_minor;
  char *grass_revision;
  int major, minor, revision;
  if ( error < 0 )
  {
    print_error( -5, "installation directory invalid: %s\n", strerror( *(int*)(__errno_location( )) ) );
  }
  sprintf( GINSTALL_DST, "GINSTALL_DST=%s", gisbase );
  putenv( GINSTALL_DST );
  sprintf( tmp, "%s/include", gisbase );
  sprintf( GINSTALL_INC, "GINSTALL_INC=%s", tmp );
  putenv( GINSTALL_INC );
  sprintf( tmp, "%s/lib", gisbase );
  sprintf( GINSTALL_LIB, "GINSTALL_LIB=%s", tmp );
  putenv( GINSTALL_LIB );
  sprintf( GEM_GRASS_DIR, "GEM_GRASS_DIR=%s", gisbase );
  putenv( GEM_GRASS_DIR );
  verstr = strdup( grass_version );
  grass_major = strtok( verstr, "." );
  grass_minor = strtok( 0, "." );
  grass_revision = strtok( 0, "." );
  major = strtol( grass_major, 0, 10 );
  minor = strtol( grass_minor, 0, 10 );
  revision = strtol( grass_revision, 0, 10 );
  free( verstr );
  atexit( &exit_tmp );
  sprintf( dir, "%s/%s", basename( package ), bins );
  error = chdir( dir );
  if ( error < 0 )
  {
    print_error( -2, "extension file binaries in '%s' not accessible: %s\n", package, strerror( *(int*)(__errno_location( )) ) );
  }
  sprintf( GEM_EXT_NAME, "GEM_EXT_NAME=%s", pkg_short_name );
  putenv( GEM_EXT_NAME );
  sprintf( tmp, "%i.%i.%i", pkg_major, pkg_minor, pkg_revision );
  sprintf( GEM_EXT_VERSION, "GEM_EXT_VERSION=%s", tmp );
  putenv( GEM_EXT_VERSION );
  dump_html( "../description", TMP_DESCR );
  dump_html( "../info", TMP_INFO );
  dump_html( "../depends", TMP_DEPS );
  dump_html( "../bugs", TMP_BUGS );
  dump_html( "../authors", TMP_AUTHORS );
  sprintf( GEM_EXT_DESCR, "GEM_EXT_DESCR=%s", TMP_DESCR );
  putenv( GEM_EXT_DESCR );
  sprintf( GEM_EXT_INFO, "GEM_EXT_INFO=%s", TMP_INFO );
  putenv( GEM_EXT_INFO );
  sprintf( GEM_EXT_DEPS, "GEM_EXT_DEPS=%s", TMP_DEPS );
  putenv( GEM_EXT_DEPS );
  sprintf( GEM_EXT_BUGS, "GEM_EXT_BUGS=%s", TMP_BUGS );
  putenv( GEM_EXT_BUGS );
  sprintf( GEM_EXT_AUTHORS, "GEM_EXT_AUTHORS=%s", TMP_AUTHORS );
  putenv( GEM_EXT_AUTHORS );
  atexit( &exit_tmp );
  check_dependencies( package, gisbase, grass_version );
  fwrite( "Installing...", 1, 13, stdout );
  f = (FILE*)fopen( "../uninstall", "r" );
  if ( f == 0 )
  {
    print_warning( "error checking for uninstall script: %s\n \t\t\t\tUninstalling this extension may leave orphaned files on your system", strerror( *(int*)(__errno_location( )) ) );
  }
  else
  {
    if ( VERBOSE )
    {
      sprintf( tmp, "cp -vf ../uninstall %s/etc/uninstall.%s ;", gisbase, pkg_short_name );
      strcpy( UNINSTALL_CMD, tmp );
    }
    else
    {
      sprintf( tmp, "cp -f ../uninstall %s/etc/uninstall.%s &&gt; %s ;", gisbase, pkg_short_name, TMP_NULL );
      strcpy( UNINSTALL_CMD, tmp );
    }
    fclose( f );
  }
  register_extension( gisbase, bins, pkg_short_name, pkg_major, pkg_minor, pkg_revision );
  check_dependencies( package, gisbase, grass_version );
  if ( major == 6 && minor <= 0 )
    register_entries_gisman( pkg_short_name, gisbase );
  register_entries_gisman2( pkg_short_name, gisbase );
  register_html( pkg_short_name, gisbase, pkg_major, pkg_minor, pkg_revision );
  if ( VERBOSE )
  {
    fprintf( stdout, "Running '%s install':\n", MAKE_CMD );
    sprintf( install_cmd, "bin/%s -f Makefile install ; \t\t\t\t\tcp -vf %s %s/etc/extensions.db ; chmod -v a+r %s/etc/extensions.db ;", MAKE_CMD, TMPDB, gisbase, gisbase );
  }
  else
    sprintf( install_cmd, "bin/%s -f Makefile -s install &&gt; %s ; \t\t\t\t\tcp -f %s %s/etc/extensions.db &&gt; %s ; chmod a+r %s/etc/extensions.db &&gt; %s ;", MAKE_CMD, TMP_NULL, TMPDB, gisbase, TMP_NULL, gisbase, TMP_NULL );
  if ( VERBOSE )
    memcpy( post_cmd, "sh ../post", 11 );
  else
    sprintf( post_cmd, "sh ../post &&gt; %s", TMP_NULL );
  sprintf( tmp, "%s %s %s %s %s %s", install_cmd, UNINSTALL_CMD, GISMAN_CMD, GISMAN2_CMD, HTML_CMD, post_cmd );
  su( gisbase, tmp );
  print_done( );
  return;
}