diff --git a/app/src/androidTest/java/ar/rulosoft/mimanganu/TestServers.java b/app/src/androidTest/java/ar/rulosoft/mimanganu/TestServers.java index 7f2a8897..4f9d1706 100644 --- a/app/src/androidTest/java/ar/rulosoft/mimanganu/TestServers.java +++ b/app/src/androidTest/java/ar/rulosoft/mimanganu/TestServers.java @@ -50,9 +50,6 @@ public void testServer() throws Exception { testGetMangas2(); } testLoadManga(); - if (serverBase.getServerID() == ServerBase.ESMANGAHERE) { - Thread.sleep(5000);//to avoid the server kick - } testInitAndGetImage(); } } diff --git a/app/src/main/java/ar/rulosoft/mimanganu/servers/KissManga.java b/app/src/main/java/ar/rulosoft/mimanganu/servers/KissManga.java index 3e6cd42c..f9778273 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/servers/KissManga.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/servers/KissManga.java @@ -204,7 +204,7 @@ public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throw } else if (filters[0].length == 1) { // single genre selection String web = genreVV + genre[0].replaceAll(" ", "-") + orderV[0]; for (int i = 0; i < genre.length; i++) { - if (contains(filters[0], i)) { + if (Util.getInstance().contains(filters[0], i)) { web = genreVV + genre[i].replaceAll(" ", "-") + orderV[filters[2][0]]; if (pageNumber > 1) { web = web + "?page=" + pageNumber; @@ -222,9 +222,9 @@ public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throw nav.addPost("mangaName", ""); nav.addPost("authorArtist", ""); for (int i = 0; i < genre.length; i++) { - if (contains(filters[0], i)) { + if (Util.getInstance().contains(filters[0], i)) { nav.addPost("genres", "1"); - } else if (contains(filters[1], i)) { + } else if (Util.getInstance().contains(filters[1], i)) { nav.addPost("genres", "2"); } else { nav.addPost("genres", "0"); diff --git a/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaPanda.java b/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaPanda.java index c4211e40..992df76d 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaPanda.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaPanda.java @@ -178,7 +178,7 @@ public ServerFilter[] getServerFilters() { public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throws Exception { String gens = ""; for (int i = 0; i < genre.length; i++) { - if (contains(filters[0], i)) { + if (Util.getInstance().contains(filters[0], i)) { gens = gens + "1"; } else { gens = gens + "0"; diff --git a/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaTown.java b/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaTown.java new file mode 100644 index 00000000..9619afd2 --- /dev/null +++ b/app/src/main/java/ar/rulosoft/mimanganu/servers/MangaTown.java @@ -0,0 +1,225 @@ +package ar.rulosoft.mimanganu.servers; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ar.rulosoft.mimanganu.R; +import ar.rulosoft.mimanganu.componentes.Chapter; +import ar.rulosoft.mimanganu.componentes.Manga; +import ar.rulosoft.mimanganu.componentes.ServerFilter; +import ar.rulosoft.mimanganu.utils.HtmlUnescape; +import ar.rulosoft.mimanganu.utils.Util; + +class MangaTown extends ServerBase { + private static final String HOST = "https://www.mangatown.com"; + + private static final String PATTERN_COVER = + "(.+?) Completed"; + private static final String PATTERN_AUTHOR = + "Author.+?\">(.+?)<"; + private static final String PATTERN_GENRE = + "
  • Genre\\(s\\):(.+?)
  • "; + private static final String PATTERN_CHAPTER = + "
  • [^<]*]*>([^<]+)[^<]+(\\d+)[^<]+?"; + private static final String PATTERN_MANGA = + "\\s* chapter.getPages()) { + page = chapter.getPages(); + } + + if (page == 1) { + return chapter.getPath(); + } + else { + return chapter.getPath() + page + ".html"; + } + } + + @Override + public String getImageFrom(Chapter chapter, int page) throws Exception { + String data; + data = getNavigatorAndFlushParameters().get(getPagesNumber(chapter, page)); + return getFirstMatch(PATTERN_IMAGE, data, "Error: failed to locate page image link"); + } + + @Override + public void chapterInit(Chapter chapter) throws Exception { + String data, pages; + data = getNavigatorAndFlushParameters().get(chapter.getPath()); + pages = getFirstMatch(PATTERN_PAGES, data, "Error: failed to get the number of pages"); + chapter.setPages(Integer.parseInt(pages)); + } + + @Override + public ArrayList search(String term) throws Exception { + ArrayList mangas = new ArrayList<>(); + String data = getNavigatorAndFlushParameters().get(HOST + "/search.php?name=" + term); + Pattern p = Pattern.compile(PATTERN_MANGA); + Matcher m = p.matcher(data); + while (m.find()) { + mangas.add(new Manga(getServerID(), HtmlUnescape.Unescape(m.group(2).trim()), "https:" + m.group(1), false)); + } + return mangas; + } + + @Override + public boolean hasList() { + return false; + } + + @Override + public ArrayList getMangas() throws Exception { + throw new UnsupportedOperationException("Error: getMangas() not implemented for MangaTown"); + } + + @Override + public boolean hasFilteredNavigation() { + return true; + } + + @Override + public ServerFilter[] getServerFilters() { + return new ServerFilter[] { + new ServerFilter("Status", fltStatus, ServerFilter.FilterType.SINGLE), + new ServerFilter("Demographic", fltDemographic, ServerFilter.FilterType.SINGLE), + new ServerFilter("Genre", fltGenre, ServerFilter.FilterType.SINGLE), + new ServerFilter("Type", fltType, ServerFilter.FilterType.SINGLE), + new ServerFilter("Order", fltOrder, ServerFilter.FilterType.SINGLE) + }; + } + + @Override + public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throws Exception { + ArrayList mangas = new ArrayList<>(); + String filter = String.format( + "%s-%s-%s-%s-%s-%s", + valDemographic[filters[1][0]], + valGenre[filters[2][0]], + "0", // year + valStatus[filters[0][0]], + "0", // a-z + valType[filters[3][0]] + ); + String order = valOrder[filters[4][0]]; + + String data = getNavigatorAndFlushParameters().get( + HOST + "/directory/" + filter + "/" + pageNumber + ".htm" + order); + Pattern p = Pattern.compile(PATTERN_MANGA); + Matcher m = p.matcher(data); + while (m.find()) { + Manga manga = new Manga( + getServerID(), HtmlUnescape.Unescape(m.group(2)), "https:" + m.group(1), false); + manga.setImages(m.group(3)); + mangas.add(manga); + } + hasMore = mangas.size() > 0; + return mangas; + } +} diff --git a/app/src/main/java/ar/rulosoft/mimanganu/servers/Mangapedia.java b/app/src/main/java/ar/rulosoft/mimanganu/servers/Mangapedia.java index ff291f17..b06ba908 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/servers/Mangapedia.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/servers/Mangapedia.java @@ -94,21 +94,21 @@ public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throw mBodyBuilder.addFormDataPart("searchTerm",""); mBodyBuilder.addFormDataPart("searchByLetter",""); for(int i = 0; i < genreV.length; i++){ - if(contains(filters[0],i)){ + if(Util.getInstance().contains(filters[0],i)){ mBodyBuilder.addFormDataPart(genreV[i],"1"); }else{ mBodyBuilder.addFormDataPart(genreV[i],"0"); } } for(int i = 0; i < subGenreV.length; i++){ - if(contains(filters[1],i)){ + if(Util.getInstance().contains(filters[1],i)){ mBodyBuilder.addFormDataPart(subGenreV[i],"1"); }else{ mBodyBuilder.addFormDataPart(subGenreV[i],"0"); } } for(int i = 0; i < typeV.length; i++){ - if(contains(filters[2],i)){ + if(Util.getInstance().contains(filters[2],i)){ mBodyBuilder.addFormDataPart(typeV[i],"1"); }else{ mBodyBuilder.addFormDataPart(typeV[i],"0"); diff --git a/app/src/main/java/ar/rulosoft/mimanganu/servers/ServerBase.java b/app/src/main/java/ar/rulosoft/mimanganu/servers/ServerBase.java index 3d817792..4d27dc04 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/servers/ServerBase.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/servers/ServerBase.java @@ -21,12 +21,15 @@ import ar.rulosoft.mimanganu.utils.Util; import ar.rulosoft.navegadores.Navigator; +/** + * The base class for all online Manga servers supported by this application. + */ public abstract class ServerBase { public static final int FROMFOLDER = 1001; - public static final int ESMANGAHERE = 3; public static final int RAWSENMANGA = 21; static final int MANGAPANDA = 1; + static final int ESMANGAHERE = 3; static final int MANGAHERE = 4; static final int MANGAFOX = 5; static final int SUBMANGA = 6; @@ -57,6 +60,7 @@ public abstract class ServerBase { static final int MANGAKAWAII = 32; static final int KUMANGA = 33; static final int MANGAPEDIA = 34; + static final int MANGATOWN = 35; static final int READCOMICONLINE = 1000; static final int READCOMICSTV = 1002; @@ -71,12 +75,25 @@ public abstract class ServerBase { private int flag; private int serverID; + /** + * Construct a new ServerBase object. + * + * @param context the context for this object + */ public ServerBase(Context context) { this.context = context; } + /** + * Get a new ServerBase object via the given identifier. + * If the passed identifier is not known, a DeadServer instance is returned. This mechanism can + * also be used for servers which wen out of commission (like EsMangaHere). + * + * @param id the identifier of the server + * @param context the context for this object + * @return a ServerBase object or a DeadServer object + */ public static ServerBase getServer(int id, Context context) { - //before remove deprecated add info on DeadServer ServerBase serverBase; switch (id) { case MANGAPANDA: @@ -166,6 +183,9 @@ public static ServerBase getServer(int id, Context context) { case VIEWCOMIC: serverBase = new ViewComic(context); break; + case MANGATOWN: + serverBase = new MangaTown(context); + break; case FROMFOLDER: serverBase = new FromFolder(context); break; @@ -176,20 +196,46 @@ public static ServerBase getServer(int id, Context context) { return serverBase; } + /** + * Return a clean Navigator instance with old POST parameters flushed. + * + * @return the Navigator object + */ public static Navigator getNavigatorAndFlushParameters() { - Navigator.navigator.flushParameter();//remove old post parameters + Navigator.navigator.flushParameter(); return Navigator.navigator; } - public static String getFirstMatch(String patron, String source, String errorMsj) throws Exception { + /** + * Returns the first regular expression match on a string, or throws an Exception. + * If the pattern is found in the source string, the first match group is returned. In case no + * match can be done, an Exception is raised with the passed errorMsg string as payload. + * + * @param patron the regular expression pattern to match + * @param source the string to check the pattern for + * @param errorMsg the descriptive error message string for the Exception raised if not match + * could be found + * @return the first match group + */ + public static String getFirstMatch(String patron, String source, String errorMsg) throws Exception { Pattern p = Pattern.compile(patron); Matcher m = p.matcher(source); if (m.find()) { return m.group(1); } - throw new Exception(errorMsj); + else { + throw new Exception(errorMsg); + } } + /** + * Returns a list of registered servers. + * Returns an array of all possible server objects (except DeadServer of course) with a given + * context. + * + * @param context the context for this object + * @return an array containing ServerBase instances - for each registered server + */ public static ServerBase[] getServers(Context context) { return (new ServerBase[]{ new TuMangaOnline(context), @@ -221,34 +267,134 @@ public static ServerBase[] getServers(Context context) { new BatoTo(context), new ReadComicOnline(context), new ViewComic(context), + new MangaTown(context), new FromFolder(context) }); } - // server + /** + * Returns the list of Manga found on the server. + * + * @return an ArrayList of Manga objects + * @throws Exception if an error occurred + */ public abstract ArrayList getMangas() throws Exception; - + /** + * Returns a list of Manga filtered by a given search term. + * + * @param term a term to search for + * @return an ArrayList of Manga objects + * @throws Exception if an error occurred + */ public abstract ArrayList search(String term) throws Exception; - // chapter + /** + * Load all available chapters for a given Manga. + * + * @param manga the Manga to find chapters for + * @param forceReload force new retrieval of chapter information + * @throws Exception if an error occurred + * @see Chapter + */ public abstract void loadChapters(Manga manga, boolean forceReload) throws Exception; - + /** + * Load available information for a given Manga. + * Load and add information to a given Manga, like: + *
      + *
    • cover image + *
    • summary + *
    • ongoing/finished + *
    • genre + *
    • chapters + *
    + * + * @param manga the Manga to find information for + * @param forceReload force new retrieval of Manga information + * @throws Exception if an error occurred + * @see Manga + * @see Chapter + */ public abstract void loadMangaInformation(Manga manga, boolean forceReload) throws Exception; - // manga + /** + * Returns the URL for the given page in a Chapter. + * Some sanity checking should be done in the override function, like non-negativity and that + * page lies within the available page numbers of the given Chapter. + * + * @param chapter a Chapter object to get the page URL for + * @param page the page number + * @return the URL to the given page of the Chapter + */ public abstract String getPagesNumber(Chapter chapter, int page); + /** + * Returns the URL for the image on a given Chapter page. + * Some sanity checking should be done in the override function, like non-negativity and that + * page lies within the available page numbers of the given Chapter. + * + * @param chapter a Chapter object to get the page image URL for + * @param page the page number + * @return the URL to the image on the given page of the Chapter + * @throws Exception if an error occurred + */ public abstract String getImageFrom(Chapter chapter, int page) throws Exception; + /** + * Initialise the Chapter information, basically the number of pages. + * + * @param chapter the Chapter object to do the initialisation for + * @throws Exception if an error occurred + */ public abstract void chapterInit(Chapter chapter) throws Exception; - // server visual + /** + * Returns a list of Manga filtered by the given filter set. + * There might be more than one result page, so pageNumber is used to get a certain result page. + * If more information is available, the hasMore variable shall be set to true to + * indicate this condition to the caller in order to fetch the next page. + * + * The filter parameter contains the current selection. The first index is given by the order + * of filters returned by getServerFilters(). The second index indicates the + * current selection for the criteria given in the first index. + * + * So if the ordering of the filter criteria is changed, make sure to reflect the change in this + * function as well. + * + * @param filters the filter set to use + * @param pageNumber the result page number for a given filter + * @return a list of Manga matching the filter criteria + * @throws Exception if an error occurred + */ public ArrayList getMangasFiltered(int[][] filters, int pageNumber) throws Exception { return new ArrayList<>(); } + /** + * Returns information if the server provides a Manga listing. + * If true is returned, getMangas() must be implemented properly. + * + * @return true if the server provides a list of Manga, false + * otherwise + */ public abstract boolean hasList(); + /** + * Searches for new chapters for a given Manga. + * Loads information from the database and fetches the current state from the server. Afterwards + * the server information is compared to the local information to check if new chapters are + * available. + * + * A fast check can be triggered to reduce load and compare time by checking only the last 20 + * chapters for differences. + * + * All detected changes are also stored in the database. + * + * @param id the Manga id to check new chapters for + * @param context the Context object to use for checking + * @param fast true to perform a fast check (first 20 chapters only) + * @return the count of new chapters found + * @throws Exception if an error occurred + */ public int searchForNewChapters(int id, Context context, boolean fast) throws Exception { int returnValue; Manga mangaDb = Database.getFullManga(context, id); @@ -328,38 +474,82 @@ public int searchForNewChapters(int id, Context context, boolean fast) throws Ex return returnValue; } + /** + * Returns the server icon resource identifier. + * + * @return the server icon resource identifier + */ public int getIcon() { return icon; } - + /** + * Sets the server icon resource identifier. + * + * @param icon the server icon resource identifier to set + */ public void setIcon(int icon) { this.icon = icon; } + /** + * Returns the flag icon resource identifier. + * + * @return the flag icon resource identifier + */ public int getFlag() { return flag; } - + /** + * Sets the flag icon resource identifier. + * + * @param flag the flag icon resource identifier to set + */ public void setFlag(int flag) { this.flag = flag; } + /** + * Returns the server resource identifier. + * + * @return the server resource identifier + */ public int getServerID() { return serverID; } - + /** + * Sets the server identifier. + * + * @param serverID the server identifier to set + */ public void setServerID(int serverID) { this.serverID = serverID; } + /** + * Returns the server name. + * + * @return the server name + */ public String getServerName() { return serverName; } - + /** + * Sets the server name. + * + * @param serverName the server name to set + */ public void setServerName(String serverName) { this.serverName = serverName; } + /** + * Returns a list of matches for a given pattern and string. + * + * @param patron the pattern to search for + * @param source the string to search + * @return a list of matches (may be empty) + * @throws Exception if an error occurred + */ public ArrayList getAllMatch(String patron, String source) throws Exception { Pattern p = Pattern.compile(patron); Matcher m = p.matcher(source); @@ -370,6 +560,14 @@ public ArrayList getAllMatch(String patron, String source) throws Except return matches; } + /** + * Returns the first match for a given pattern and string or a default text. + * + * @param patron the pattern to search for + * @param source the string to search + * @param mDefault the default string to return in case no match was found + * @return the first match or the value defined by mDefault + */ public String getFirstMatchDefault(String patron, String source, String mDefault) { Pattern p = Pattern.compile(patron); Matcher m = p.matcher(source); @@ -380,22 +578,48 @@ public String getFirstMatchDefault(String patron, String source, String mDefault } } + /** + * Returns information if a referrer is needed for image loading. + * + * @return true if a referrer is needed + */ public boolean needRefererForImages() { return true; } + /** + * Returns information if the server offers filtered navigation. + * If true is returned, getMangasFiltered() must be implemented properly. + * + * @return true if filtered navigation is offered + */ public boolean hasFilteredNavigation() { return true; } + /** + * Returns the type of filtering supported. + * + * @return either VISUAL or TEXT + */ public FilteredType getFilteredType() { return FilteredType.VISUAL; } + /** + * Returns the supported server filters for this server. + * + * @return a list of ServerFilter supported by this server + */ public ServerFilter[] getServerFilters() { return new ServerFilter[]{}; } + /** + * Returns the most basic filter set for this server. + * + * @return the basic filter for this server + */ public int[][] getBasicFilter() { ServerFilter[] filters = getServerFilters(); int[][] result = new int[filters.length][]; @@ -410,27 +634,40 @@ public int[][] getBasicFilter() { return result; } + /** + * Returns information the server needs a login. + * + * @return true if a login is needed + */ public boolean needLogin() { return false; } + /** + * Returns information if credentials are present. + * Should return true to disable querying credentials. + * + * @return true if credentials are present + */ public boolean hasCredentials() { return true; } + /** + * Tests if the given login data is working. + * + * @param user the user to log in + * @param passwd the password to use for logging in + * @return true if the login succeeded, false otherwise + * @throws Exception if an error occurred + */ public boolean testLogin(String user, String passwd) throws Exception { return false; } - public boolean contains(int[] array, int value) { - for (int i : array) { - if (i == value) { - return true; - } - } - return false; - } - + /** + * An enumeration for the type of filtering supported. + */ public enum FilteredType {VISUAL, TEXT} class CreateGroupByMangaNotificationsTask extends AsyncTask { @@ -494,5 +731,4 @@ protected void onPostExecute(Integer result) { super.onPostExecute(result); } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/ar/rulosoft/mimanganu/utils/HtmlUnescape.java b/app/src/main/java/ar/rulosoft/mimanganu/utils/HtmlUnescape.java index 6455fd68..55b7454e 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/utils/HtmlUnescape.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/utils/HtmlUnescape.java @@ -15,6 +15,7 @@ public class HtmlUnescape { put("", "\n"); put(""", "\""); put("&", "&"); + put("'", "'"); // End of definitions }}; diff --git a/app/src/main/java/ar/rulosoft/mimanganu/utils/Util.java b/app/src/main/java/ar/rulosoft/mimanganu/utils/Util.java index 2a3d8b0b..7a20bc3b 100644 --- a/app/src/main/java/ar/rulosoft/mimanganu/utils/Util.java +++ b/app/src/main/java/ar/rulosoft/mimanganu/utils/Util.java @@ -599,6 +599,15 @@ public void removeAllCookies(Context context) { } } + public boolean contains(int[] array, int value) { + for (int i : array) { + if (i == value) { + return true; + } + } + return false; + } + private static class LazyHolder { private static final Util utilInstance = new Util(); } diff --git a/app/src/main/java/ar/rulosoft/navegadores/Navigator.java b/app/src/main/java/ar/rulosoft/navegadores/Navigator.java index 909534f1..eea4b328 100644 --- a/app/src/main/java/ar/rulosoft/navegadores/Navigator.java +++ b/app/src/main/java/ar/rulosoft/navegadores/Navigator.java @@ -452,6 +452,7 @@ public TrustManager[] getTrustManagers(Context context) { keyStore_n.setCertificateEntry("mangahereco", loadCertificateFromRaw(R.raw.mangahereco,context)); keyStore_n.setCertificateEntry("mangafoxme", loadCertificateFromRaw(R.raw.mangafoxme,context)); keyStore_n.setCertificateEntry("mangaherecoImages", loadCertificateFromRaw(R.raw.mangaherecoimages, context)); + keyStore_n.setCertificateEntry("mangatowncom", loadCertificateFromRaw(R.raw.mangatowncom,context)); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore_n); return tmf.getTrustManagers(); diff --git a/app/src/main/res/drawable-mdpi/mangatown.png b/app/src/main/res/drawable-mdpi/mangatown.png new file mode 100644 index 00000000..93143a43 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mangatown.png differ diff --git a/app/src/main/res/raw/mangatowncom b/app/src/main/res/raw/mangatowncom new file mode 100644 index 00000000..b5f3f355 --- /dev/null +++ b/app/src/main/res/raw/mangatowncom @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFUDCCBDigAwIBAgIQd6oSat9TdgRHcgeYoU9EeDANBgkqhkiG9w0BAQsFADCB +kDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNV +BAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD +QTAeFw0xNzA5MTIwMDAwMDBaFw0xODA5MTIyMzU5NTlaMFYxITAfBgNVBAsTGERv +bWFpbiBDb250cm9sIFZhbGlkYXRlZDEVMBMGA1UECxMMRXNzZW50aWFsU1NMMRow +GAYDVQQDExF3d3cubWFuZ2F0b3duLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANTyETPNTaAc6lX6gGCsVd0FhkVLujJAec1PyXeMKpoZbt/xEGGs +aPoDE/qTGbdMGPwobMY7hlcccXUOcoulp8GlDKJQJ8gajT9PHxDKgfP8oe3Kvm4X +ukUpqIo4qJ5O+NzpqiIWHRy18vbn6QrDYq5ekevszkqRnZFGxeErryuHXM7miFzn +vgEmN61NRiJy8NIRPP1swLnyK8d3LzXOG2Cl9jyUSqLO05vrMK2uEb4qApmWbyvd +nBw48m277/6sPfVXGB2zHj7fVrSqrUiWeVocVhDxWGElGLGqpN05GTcBy1kaX0sQ +07jByIv3f2T/HGJt8RNggWagEOhWS/AgEisCAwEAAaOCAd0wggHZMB8GA1UdIwQY +MBaAFJCvajqUWgvYkOoSVnPfQ7Q6KNrnMB0GA1UdDgQWBBTdsad0FwOvXQt7GIC9 +UuQo3wvsxTAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzAr +MCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZn +gQwBAgEwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5jb21vZG9jYS5jb20v +Q09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNybDCBhQYI +KwYBBQUHAQEEeTB3ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LmNvbW9kb2NhLmNv +bS9DT01PRE9SU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wKwYDVR0RBCQwIoIR +d3d3Lm1hbmdhdG93bi5jb22CDW1hbmdhdG93bi5jb20wDQYJKoZIhvcNAQELBQAD +ggEBACXQAENVv52sRFTXnSE5QStdNQAoW4Kyklc6bmR4z/sQGRa3RQgdC1YWGnD0 +3XT/Xl1bQs9WFa6swA50nvJVtW4SVxxecBh9dd62aZ0LZ/w+syNcX+dtSJkc5LQY +Zfg9z5tcv5vRx7LAFd7gjWYD912HVI80yjCSDXYyyvBiKGO1bOX7jkgYdYZUyvNU +tmhiO0Gih+lSz/gRfmT7+NEykrXxTPTMQZgYbATrIPBHpbYPOhnM4FOTu93/6I1K +XPDddjbRoqd4gMSRp01bBdmYyrnwSCANHll9dd4B3hmltFbb2kGb+SSZ6KqlcG6e +CzColowl/n1WabkQtS6SaLEG5QM= +-----END CERTIFICATE-----