コード例 #1
0
  public ResPackage loadMainPkg(ResTable resTable, ExtFile apkFile) throws AndrolibException {
    LOGGER.info("Loading resource table...");
    ResPackage[] pkgs = getResPackagesFromApk(apkFile, resTable, sKeepBroken);
    ResPackage pkg = null;

    switch (pkgs.length) {
      case 1:
        pkg = pkgs[0];
        break;
      case 2:
        if (pkgs[0].getName().equals("android")) {
          LOGGER.warning("Skipping \"android\" package group");
          pkg = pkgs[1];
        } else if (pkgs[0].getName().equals("com.htc")) {
          LOGGER.warning("Skipping \"htc\" stupid package group");
          pkg = pkgs[1];
        }
        break;
    }

    if (pkg == null) {
      throw new AndrolibException("Arsc files with zero or multiple packages");
    }

    resTable.addPackage(pkg, true);
    LOGGER.info("Loaded.");
    return pkg;
  }
コード例 #2
0
  public boolean buildManifest(ExtFile appDir, Map<String, Object> usesFramework)
      throws BrutException {
    try {
      if (!new File(appDir, "AndroidManifest.xml").exists()) {
        return false;
      }
      if (!apkOptions.forceBuildAll) {
        LOGGER.info("Checking whether resources has changed...");
      }

      File apkDir = new File(appDir, APK_DIRNAME);

      if (apkOptions.debugMode) {
        mAndRes.remove_application_debug(new File(apkDir, "AndroidManifest.xml").getAbsolutePath());
      }

      if (apkOptions.forceBuildAll
          || isModified(
              newFiles(APK_MANIFEST_FILENAMES, appDir), newFiles(APK_MANIFEST_FILENAMES, apkDir))) {
        LOGGER.info("Building AndroidManifest.xml...");

        File apkFile = File.createTempFile("APKTOOL", null);
        apkFile.delete();

        File ninePatch = new File(appDir, "9patch");
        if (!ninePatch.exists()) {
          ninePatch = null;
        }

        mAndRes.aaptPackage(
            apkFile,
            new File(appDir, "AndroidManifest.xml"),
            null,
            ninePatch,
            null,
            parseUsesFramework(usesFramework));

        Directory tmpDir = new ExtFile(apkFile).getDirectory();
        tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES);
      }
      return true;
    } catch (IOException | DirectoryException ex) {
      throw new AndrolibException(ex);
    } catch (AndrolibException ex) {
      LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file.");
      return buildManifestRaw(appDir);
    }
  }
コード例 #3
0
 private void stopTimeout(MessageContext context, String args) {
   if (args.isEmpty()) {
     apiClient.sendMessage(
         loc.localize("commands.mod.stoptimeout.response.invalid"), context.getChannel());
     return;
   }
   String uid = args;
   if (uid.length() > 4) {
     if (uid.startsWith("<@")) {
       uid = uid.substring(2, uid.length() - 1);
     }
     Server server = context.getServer();
     User user = apiClient.getUserById(uid, server);
     if (user == NO_USER) {
       user = new User("UNKNOWN", uid, "", null);
     }
     LOGGER.info(
         "{} ({}) is attempting to cancel timeout for {} ({}) in {} ({})",
         context.getAuthor().getUsername(),
         context.getAuthor().getId(),
         user.getUsername(),
         user.getId(),
         server.getName(),
         server.getId());
     cancelTimeout(user, server, context.getChannel());
   } else {
     apiClient.sendMessage(
         loc.localize("commands.mod.stoptimeout.response.invalid"), context.getChannel());
   }
 }
コード例 #4
0
 private void refreshTimeoutOnEvade(User user, Server server) {
   ServerTimeout timeout =
       SafeNav.of(serverStorage.get(server.getId()))
           .next(TempServerConfig::getServerTimeouts)
           .next(ServerTimeoutStorage::getTimeouts)
           .next(timeouts -> timeouts.get(user.getId()))
           .get();
   if (timeout == null) {
     LOGGER.warn(
         "Attempted to refresh a timeout on a user who was not timed out! {} ({})",
         user.getUsername(),
         user.getId());
     return;
   }
   LOGGER.info(
       "User {} ({}) attempted to evade a timeout on {} ({})!",
       user.getUsername(),
       user.getId(),
       server.getName(),
       server.getId());
   Channel channel = apiClient.getChannelById(server.getId(), server);
   apiClient.sendMessage(
       loc.localize(
           "listener.mod.timeout.on_evasion",
           user.getId(),
           formatDuration(Duration.between(Instant.now(), timeout.getEndTime())),
           formatInstant(timeout.getEndTime())),
       channel);
   applyTimeoutRole(user, server, channel);
 }
コード例 #5
0
 public void onTimeoutExpire(User user, Server server) {
   String serverId = server.getId();
   TempServerConfig serverConfig = serverStorage.get(serverId);
   if (serverConfig == null) {
     serverConfig = new TempServerConfig(serverId);
     serverStorage.put(serverId, serverConfig);
   }
   ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
   if (storage != null) {
     ServerTimeout timeout = storage.getTimeouts().remove(user.getId());
     if (timeout != null) {
       saveServerConfig(serverConfig);
       LOGGER.info(
           "Expiring timeout for {} ({}) in {} ({})",
           user.getUsername(),
           user.getId(),
           server.getName(),
           server.getId());
       if (apiClient.getUserById(user.getId(), server) != NO_USER) {
         apiClient.sendMessage(
             loc.localize("message.mod.timeout.expire", user.getId()), server.getId());
       }
       removeTimeoutRole(user, server, apiClient.getChannelById(server.getId()));
       return;
     }
   }
   LOGGER.warn(
       "Unable to expire: find server or timeout entry for {} ({}) in {} ({})",
       user.getUsername(),
       user.getId(),
       server.getName(),
       server.getId());
 }
コード例 #6
0
 public boolean buildSourcesJava(File appDir) throws AndrolibException {
   File javaDir = new File(appDir, "src");
   if (!javaDir.exists()) {
     return false;
   }
   File dex = new File(appDir, APK_DIRNAME + "/classes.dex");
   if (!apkOptions.forceBuildAll) {
     LOGGER.info("Checking whether sources has changed...");
   }
   if (apkOptions.forceBuildAll || isModified(javaDir, dex)) {
     LOGGER.info("Building java sources...");
     dex.delete();
     new AndrolibJava().build(javaDir, dex);
   }
   return true;
 }
コード例 #7
0
  public void build(ExtFile appDir, File outFile) throws BrutException {
    LOGGER.info("Using Apktool " + Androlib.getVersion());

    Map<String, Object> meta = readMetaFile(appDir);
    Object t1 = meta.get("isFrameworkApk");
    apkOptions.isFramework = (t1 == null ? false : (Boolean) t1);
    apkOptions.resourcesAreCompressed =
        meta.get("compressionType") == null
            ? false
            : Boolean.valueOf(meta.get("compressionType").toString());

    mAndRes.setSdkInfo((Map<String, String>) meta.get("sdkInfo"));
    mAndRes.setPackageId((Map<String, String>) meta.get("packageInfo"));
    mAndRes.setPackageInfo((Map<String, String>) meta.get("packageInfo"));
    mAndRes.setVersionInfo((Map<String, String>) meta.get("versionInfo"));

    if (outFile == null) {
      String outFileName = (String) meta.get("apkFileName");
      outFile =
          new File(
              appDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName));
    }

    new File(appDir, APK_DIRNAME).mkdirs();
    buildSources(appDir);
    buildNonDefaultSources(appDir);
    buildResources(appDir, (Map<String, Object>) meta.get("usesFramework"));
    buildLib(appDir);
    buildCopyOriginalFiles(appDir);
    buildApk(appDir, outFile);

    // we must go after the Apk is built, and copy the files in via Zip
    // this is because Aapt won't add files it doesn't know (ex unknown files)
    buildUnknownFiles(appDir, outFile, meta);
  }
コード例 #8
0
 public void decodeSourcesSmali(
     File apkFile,
     File outDir,
     String filename,
     boolean debug,
     String debugLinePrefix,
     boolean bakdeb,
     int api)
     throws AndrolibException {
   try {
     File smaliDir;
     if (filename.equalsIgnoreCase("classes.dex")) {
       smaliDir = new File(outDir, SMALI_DIRNAME);
     } else {
       smaliDir =
           new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
     }
     OS.rmdir(smaliDir);
     smaliDir.mkdirs();
     LOGGER.info("Baksmaling " + filename + "...");
     SmaliDecoder.decode(apkFile, smaliDir, filename, debug, debugLinePrefix, bakdeb, api);
   } catch (BrutException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #9
0
  public void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir)
      throws AndrolibException {

    Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder();
    ResFileDecoder fileDecoder = duo.m1;

    // Set ResAttrDecoder
    duo.m2.setAttrDecoder(new ResAttrDecoder());
    ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();

    // Fake ResPackage
    attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null));

    Directory inApk, out;
    try {
      inApk = apkFile.getDirectory();
      out = new FileDirectory(outDir);

      LOGGER.info("Decoding AndroidManifest.xml with only framework resources...");
      fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");

    } catch (DirectoryException ex) {
      throw new AndrolibException(ex);
    }
  }
コード例 #10
0
  public boolean buildResourcesFull(File appDir, Map<String, Object> usesFramework)
      throws AndrolibException {
    try {
      if (!new File(appDir, "res").exists()) {
        return false;
      }
      if (!apkOptions.forceBuildAll) {
        LOGGER.info("Checking whether resources has changed...");
      }
      File apkDir = new File(appDir, APK_DIRNAME);
      if (apkOptions.forceBuildAll
          || isModified(
              newFiles(APP_RESOURCES_FILENAMES, appDir),
              newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
        LOGGER.info("Building resources...");

        File apkFile = File.createTempFile("APKTOOL", null);
        apkFile.delete();

        File ninePatch = new File(appDir, "9patch");
        if (!ninePatch.exists()) {
          ninePatch = null;
        }
        mAndRes.aaptPackage(
            apkFile,
            new File(appDir, "AndroidManifest.xml"),
            new File(appDir, "res"),
            ninePatch,
            null,
            parseUsesFramework(usesFramework));

        Directory tmpDir = new ExtFile(apkFile).getDirectory();
        tmpDir.copyToDir(
            apkDir,
            tmpDir.containsDir("res")
                ? APK_RESOURCES_FILENAMES
                : APK_RESOURCES_WITHOUT_RES_FILENAMES);

        // delete tmpDir
        apkFile.delete();
      }
      return true;
    } catch (IOException | BrutException ex) {
      throw new AndrolibException(ex);
    }
  }
コード例 #11
0
 public boolean buildSourcesSmali(File appDir, String folder, String filename)
     throws AndrolibException {
   ExtFile smaliDir = new ExtFile(appDir, folder);
   if (!smaliDir.exists()) {
     return false;
   }
   File dex = new File(appDir, APK_DIRNAME + "/" + filename);
   if (!apkOptions.forceBuildAll) {
     LOGGER.info("Checking whether sources has changed...");
   }
   if (apkOptions.forceBuildAll || isModified(smaliDir, dex)) {
     LOGGER.info("Smaling " + folder + " folder into " + filename + "...");
     dex.delete();
     SmaliBuilder.build(smaliDir, dex, apkOptions.debugMode);
   }
   return true;
 }
コード例 #12
0
 @Override
 protected void onActTerminated(Act act, Audit audit) {
   super.onActTerminated(act, audit);
   if (getException() == null) {
     sendAuditResultEmail(act, getLocale());
   }
   LOGGER.info("Audit scenario terminated on " + this.scenarioName);
 }
コード例 #13
0
 public void decodeResourcesRaw(ExtFile apkFile, File outDir) throws AndrolibException {
   try {
     LOGGER.info("Copying raw resources...");
     apkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
   } catch (DirectoryException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #14
0
  public void decode(ResTable resTable, ExtFile apkFile, File outDir) throws AndrolibException {
    Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
    ResFileDecoder fileDecoder = duo.m1;
    ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();

    attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());

    Directory inApk, in = null, out;
    try {
      inApk = apkFile.getDirectory();
      out = new FileDirectory(outDir);

      LOGGER.info("Decoding AndroidManifest.xml with resources...");

      fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml");

      if (inApk.containsDir("res")) {
        in = inApk.getDir("res");
      }
      out = out.createDir("res");
    } catch (DirectoryException ex) {
      throw new AndrolibException(ex);
    }

    ExtMXSerializer xmlSerializer = getResXmlSerializer();
    for (ResPackage pkg : resTable.listMainPackages()) {
      attrDecoder.setCurrentPackage(pkg);

      LOGGER.info("Decoding file-resources...");
      for (ResResource res : pkg.listFiles()) {
        fileDecoder.decode(res, in, out);
      }

      LOGGER.info("Decoding values */* XMLs...");
      for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
        generateValuesFile(valuesFile, out, xmlSerializer);
      }
      generatePublicXml(pkg, out, xmlSerializer);
      LOGGER.info("Done.");
    }

    AndrolibException decodeError = duo.m2.getFirstError();
    if (decodeError != null) {
      throw decodeError;
    }
  }
コード例 #15
0
 public boolean isDurationExceedsDelay() {
   long currentDuration = new Date().getTime() - getStartDate().getTime();
   if (currentDuration > delay) {
     LOGGER.info("Audit Duration has exceeded synchronous delay " + delay);
     isAuditTerminatedAfterTimeout = true;
     return true;
   }
   return false;
 }
コード例 #16
0
 public boolean applyTimeout(
     User issuingUser, Channel noticeChannel, Server server, User user, Duration duration) {
   String serverId = server.getId();
   if (duration != null && !duration.isNegative() && !duration.isZero()) {
     ServerTimeout timeout =
         new ServerTimeout(
             duration,
             Instant.now(),
             user.getId(),
             serverId,
             user.getUsername(),
             issuingUser.getId());
     TempServerConfig serverConfig = serverStorage.get(serverId);
     if (serverConfig == null) {
       serverConfig = new TempServerConfig(serverId);
       serverStorage.put(serverId, serverConfig);
     }
     ServerTimeoutStorage storage = serverConfig.getServerTimeouts();
     if (storage == null) {
       storage = new ServerTimeoutStorage();
       serverConfig.setServerTimeouts(storage);
     }
     if (applyTimeoutRole(user, server, noticeChannel)) {
       storage.getTimeouts().put(user.getId(), timeout);
       ScheduledFuture future =
           timeoutService.schedule(
               () -> onTimeoutExpire(user, server), duration.getSeconds(), TimeUnit.SECONDS);
       timeout.setTimerFuture(future);
       saveServerConfig(serverConfig);
       String durationStr = formatDuration(duration);
       String instantStr = formatInstant(timeout.getEndTime());
       String msg =
           loc.localize(
               "commands.mod.timeout.response",
               user.getUsername(),
               user.getId(),
               durationStr,
               instantStr);
       apiClient.sendMessage(msg, noticeChannel);
       LOGGER.info(
           "[{}] '{}': Timing out {} ({}) for {} (until {}), issued by {} ({})",
           serverId,
           server.getName(),
           user.getUsername(),
           user.getId(),
           durationStr,
           instantStr,
           issuingUser.getUsername(),
           issuingUser.getId());
     }
     //  No else with error - applyTimeoutRole does that for us
     return true;
   } else {
     LOGGER.warn("Invalid duration format");
   }
   return false;
 }
コード例 #17
0
 public void decodeManifestRaw(ExtFile apkFile, File outDir) throws AndrolibException {
   try {
     Directory apk = apkFile.getDirectory();
     LOGGER.info("Copying raw manifest...");
     apkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES);
   } catch (DirectoryException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #18
0
 public void decodeSourcesRaw(ExtFile apkFile, File outDir, String filename)
     throws AndrolibException {
   try {
     LOGGER.info("Copying raw classes.dex file...");
     apkFile.getDirectory().copyToDir(outDir, filename);
   } catch (DirectoryException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #19
0
 public boolean buildManifestRaw(ExtFile appDir) throws AndrolibException {
   try {
     File apkDir = new File(appDir, APK_DIRNAME);
     LOGGER.info("Copying raw AndroidManifest.xml...");
     appDir.getDirectory().copyToDir(apkDir, APK_MANIFEST_FILENAMES);
     return true;
   } catch (DirectoryException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #20
0
 public AuditSiteThread(
     String siteUrl,
     AuditService auditService,
     Act act,
     Set<Parameter> parameterSet,
     Locale locale) {
   super(auditService, act, parameterSet, locale);
   this.siteUrl = siteUrl;
   LOGGER.info("Launching audit site on " + this.siteUrl);
 }
コード例 #21
0
  public void installFramework(File frameFile, String tag) throws AndrolibException {
    InputStream in = null;
    ZipOutputStream out = null;
    try {
      ZipFile zip = new ZipFile(frameFile);
      ZipEntry entry = zip.getEntry("resources.arsc");

      if (entry == null) {
        throw new AndrolibException("Can't find resources.arsc file");
      }

      in = zip.getInputStream(entry);
      byte[] data = IOUtils.toByteArray(in);

      ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
      publicizeResources(data, arsc.getFlagsOffsets());

      File outFile =
          new File(
              getFrameworkDir(),
              String.valueOf(arsc.getOnePackage().getId())
                  + (tag == null ? "" : '-' + tag)
                  + ".apk");

      out = new ZipOutputStream(new FileOutputStream(outFile));
      out.setMethod(ZipOutputStream.STORED);
      CRC32 crc = new CRC32();
      crc.update(data);
      entry = new ZipEntry("resources.arsc");
      entry.setSize(data.length);
      entry.setCrc(crc.getValue());
      out.putNextEntry(entry);
      out.write(data);

      LOGGER.info("Framework installed to: " + outFile);
    } catch (ZipException ex) {
      throw new AndrolibException(ex);
    } catch (IOException ex) {
      throw new AndrolibException(ex);
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException ex) {
        }
      }
      if (out != null) {
        try {
          out.close();
        } catch (IOException ex) {
        }
      }
    }
  }
コード例 #22
0
 public AuditPageThread(
     String pageUrl,
     AuditService auditService,
     Act act,
     Set<Parameter> parameterSet,
     Locale locale,
     int delay) {
   super(auditService, act, parameterSet, locale, delay);
   this.pageUrl = pageUrl;
   LOGGER.info("Launching audit Page on " + pageUrl);
 }
コード例 #23
0
  public ResPackage loadFrameworkPkg(ResTable resTable, int id, String frameTag)
      throws AndrolibException {
    File apk = getFrameworkApk(id, frameTag);

    LOGGER.info("Loading resource table from file: " + apk);
    ResPackage[] pkgs = getResPackagesFromApk(new ExtFile(apk), resTable, true);

    if (pkgs.length != 1) {
      throw new AndrolibException("Arsc files with zero or multiple packages");
    }

    ResPackage pkg = pkgs[0];
    if (pkg.getId() != id) {
      throw new AndrolibException(
          "Expected pkg of id: " + String.valueOf(id) + ", got: " + pkg.getId());
    }

    resTable.addPackage(pkg, false);
    LOGGER.info("Loaded.");
    return pkg;
  }
コード例 #24
0
 public boolean buildResourcesRaw(ExtFile appDir) throws AndrolibException {
   try {
     if (!new File(appDir, "resources.arsc").exists()) {
       return false;
     }
     File apkDir = new File(appDir, APK_DIRNAME);
     if (!apkOptions.forceBuildAll) {
       LOGGER.info("Checking whether resources has changed...");
     }
     if (apkOptions.forceBuildAll
         || isModified(
             newFiles(APK_RESOURCES_FILENAMES, appDir),
             newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
       LOGGER.info("Copying raw resources...");
       appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES);
     }
     return true;
   } catch (DirectoryException ex) {
     throw new AndrolibException(ex);
   }
 }
コード例 #25
0
 public AuditScenarioThread(
     String scenarioName,
     String scenario,
     AuditService auditService,
     Act act,
     Set<Parameter> parameterSet,
     Locale locale) {
   super(auditService, act, parameterSet, locale);
   this.scenario = scenario;
   this.scenarioName = scenarioName;
   LOGGER.info("Launching audit scenario " + this.scenarioName);
 }
コード例 #26
0
 public void buildCopyOriginalFiles(File appDir) throws AndrolibException {
   if (apkOptions.copyOriginalFiles) {
     File originalDir = new File(appDir, "original");
     if (originalDir.exists()) {
       try {
         LOGGER.info("Copy original files...");
         Directory in = (new ExtFile(originalDir)).getDirectory();
         if (in.containsFile("AndroidManifest.xml")) {
           LOGGER.info("Copy AndroidManifest.xml...");
           in.copyToDir(new File(appDir, APK_DIRNAME), "AndroidManifest.xml");
         }
         if (in.containsDir("META-INF")) {
           LOGGER.info("Copy META-INF...");
           in.copyToDir(new File(appDir, APK_DIRNAME), "META-INF");
         }
       } catch (DirectoryException ex) {
         throw new AndrolibException(ex);
       }
     }
   }
 }
コード例 #27
0
 public AuditPageUploadThread(
     Map<String, String> pageMap,
     AuditService auditService,
     Act act,
     Set<Parameter> parameterSet,
     Locale locale,
     int delay) {
   super(auditService, act, parameterSet, locale, delay);
   if (pageMap != null) {
     this.pageMap.putAll(pageMap);
     LOGGER.info("Launching audit files on " + pageMap.keySet());
   }
 }
コード例 #28
0
 public void loadServerConfigFiles() {
   if (!Files.exists(serverStorageDir)) {
     LOGGER.info("Server storage directory doesn't exist, not loading anything");
     return;
   }
   try (Stream<Path> files = Files.list(serverStorageDir)) {
     files
         .filter(p -> p.getFileName().toString().endsWith(".json"))
         .forEach(this::loadServerConfig);
   } catch (IOException e) {
     LOGGER.warn("Unable to load server storage files", e);
     return;
   }
 }
コード例 #29
0
 public AuditGroupOfPagesThread(
     String siteUrl,
     List<String> pageUrlList,
     AuditService auditService,
     Act act,
     Set<Parameter> parameterSet,
     Locale locale,
     int delay) {
   super(auditService, act, parameterSet, locale, delay);
   this.siteUrl = siteUrl;
   if (pageUrlList != null) {
     this.pageUrlList.addAll(pageUrlList);
   }
   LOGGER.info("Launching audit group of Pages on " + this.pageUrlList);
 }
コード例 #30
0
 public void buildApk(File appDir, File outApk) throws AndrolibException {
   LOGGER.info("Building apk file...");
   if (outApk.exists()) {
     outApk.delete();
   } else {
     File outDir = outApk.getParentFile();
     if (outDir != null && !outDir.exists()) {
       outDir.mkdirs();
     }
   }
   File assetDir = new File(appDir, "assets");
   if (!assetDir.exists()) {
     assetDir = null;
   }
   mAndRes.aaptPackage(outApk, null, null, new File(appDir, APK_DIRNAME), assetDir, null);
 }