Fake log integration (#39)

* Handle technical break error

* Add login by pass email with log host

* Use in FirstAccountLogin fully Vulcan facade

* Add SnP interface
This commit is contained in:
Mikołaj Pich
2017-12-13 22:15:39 +01:00
committed by Rafał Borcz
parent c111e43f18
commit 15a1662ac5
36 changed files with 372 additions and 187 deletions

View File

@ -7,7 +7,6 @@ apply plugin: 'com.github.dcendents.android-maven'
compileJava.options.encoding = "UTF-8"
compileTestJava.options.encoding = "UTF-8"
ext {
PUBLISH_GROUP_ID = GROUP_ID
PUBLISH_ARTIFACT_ID = 'api'
@ -29,7 +28,7 @@ jacocoTestReport {
}
dependencies {
implementation 'org.jsoup:jsoup:1.10.3'
implementation 'org.jsoup:jsoup:1.11.2'
implementation 'org.apache.commons:commons-lang3:3.6'
testImplementation 'junit:junit:4.12'

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.api;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.util.List;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public interface SnP {
void setProtocolSchema(String schema);
String getLogHost();
void setLogHost(String hostname);
String getSymbol();
String getId();
void storeContextCookies() throws IOException, NotLoggedInErrorException;
Cookies getCookiesObject();
String getRowDataChildValue(Element e, int index);
Document getSnPPageDocument(String url) throws IOException;
List<Semester> getSemesters() throws IOException;
List<Semester> getSemesters(Document gradesPage);
Semester getCurrentSemester(List<Semester> semesterList);
}

View File

@ -10,16 +10,20 @@ import java.util.List;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
public class StudentAndParent extends Api {
public class StudentAndParent extends Api implements SnP {
private String startPageUrl = "https://uonetplus.vulcan.net.pl/{symbol}/Start.mvc/Index";
private static final String startPageUrl = "{schema}://uonetplus.{host}/{symbol}/Start.mvc/Index";
private String baseUrl = "https://uonetplus-opiekun.vulcan.net.pl/{symbol}/{ID}/";
private static final String baseUrl = "{schema}://uonetplus-opiekun.{host}/{symbol}/{ID}/";
private static final String SYMBOL_PLACEHOLDER = "{symbol}";
private static final String GRADES_PAGE_URL = "Oceny/Wszystkie";
protected String logHost = "vulcan.net.pl";
private String protocolSchema = "https";
private String symbol;
private String id;
@ -34,16 +38,35 @@ public class StudentAndParent extends Api {
this.id = id;
}
public String getGradesPageUrl() {
return baseUrl + GRADES_PAGE_URL;
public void setProtocolSchema(String schema) {
this.protocolSchema = schema;
}
public String getBaseUrl() {
return baseUrl;
public String getLogHost() {
return logHost;
}
public void setLogHost(String hostname) {
this.logHost = hostname;
}
public String getStartPageUrl() {
return startPageUrl;
return startPageUrl
.replace("{schema}", protocolSchema)
.replace("{host}", logHost)
.replace(SYMBOL_PLACEHOLDER, getSymbol());
}
public String getBaseUrl() {
return baseUrl
.replace("{schema}", protocolSchema)
.replace("{host}", logHost)
.replace(SYMBOL_PLACEHOLDER, getSymbol())
.replace("{ID}", getId());
}
public String getGradesPageUrl() {
return getBaseUrl() + GRADES_PAGE_URL;
}
public String getSymbol() {
@ -60,11 +83,11 @@ public class StudentAndParent extends Api {
public String getSnpPageUrl() throws IOException, NotLoggedInErrorException {
if (null != getId()) {
return getBaseUrl().replace(SYMBOL_PLACEHOLDER, getSymbol()).replace("{ID}", getId());
return getBaseUrl();
}
// get url to uonetplus-opiekun.vulcan.net.pl
Document startPage = getPageByUrl(getStartPageUrl().replace(SYMBOL_PLACEHOLDER, getSymbol()));
Document startPage = getPageByUrl(getStartPageUrl());
Element studentTileLink = startPage.select(".panel.linkownia.pracownik.klient > a").first();
if (null == studentTileLink) {
@ -79,7 +102,7 @@ public class StudentAndParent extends Api {
}
public String getExtractedIdFromUrl(String snpPageUrl) throws NotLoggedInErrorException {
String[] path = snpPageUrl.split("vulcan.net.pl/")[1].split("/");
String[] path = snpPageUrl.split(getLogHost() + "/")[1].split("/");
if (4 != path.length) {
throw new NotLoggedInErrorException();

View File

@ -12,6 +12,7 @@ import io.github.wulkanowy.api.login.BadCredentialsException;
import io.github.wulkanowy.api.login.Login;
import io.github.wulkanowy.api.login.LoginErrorException;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.login.VulcanOfflineException;
import io.github.wulkanowy.api.notes.AchievementsList;
import io.github.wulkanowy.api.notes.NotesList;
import io.github.wulkanowy.api.school.SchoolInfo;
@ -26,29 +27,80 @@ public class Vulcan extends Api {
private String symbol;
private StudentAndParent snp;
private SnP snp;
public void login(Cookies cookies, String symbol) {
private String protocolSchema = "https";
private String logHost = "vulcan.net.pl";
private String email;
public Vulcan login(Cookies cookies, String symbol) {
this.cookies = cookies;
this.symbol = symbol;
return this;
}
public void login(String email, String password, String symbol)
throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException {
Login login = new Login(new Cookies());
String realSymbol = login.login(email, password, symbol);
public Vulcan login(String email, String password, String symbol)
throws BadCredentialsException, AccountPermissionException,
LoginErrorException, IOException, VulcanOfflineException {
Login login = getLoginObject();
setFullEndpointInfo(email);
login.setProtocolSchema(protocolSchema);
login.setLogHost(logHost);
String realSymbol = login.login(this.email, password, symbol);
login(login.getCookiesObject(), realSymbol);
return this;
}
public void login(String email, String password, String symbol, String id)
throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException {
public Vulcan login(String email, String password, String symbol, String id)
throws BadCredentialsException, AccountPermissionException,
LoginErrorException, IOException, VulcanOfflineException {
login(email, password, symbol);
this.id = id;
return this;
}
public StudentAndParent getStudentAndParent() throws IOException, NotLoggedInErrorException {
public String getProtocolSchema() {
return protocolSchema;
}
public String getLogHost() {
return logHost;
}
public String getEmail() {
return email;
}
public Login getLoginObject() {
return new Login(new Cookies());
}
public Vulcan setFullEndpointInfo(String email) {
String[] creds = email.split("\\\\");
this.email = email;
if (creds.length >= 2) {
String[] url = creds[0].split("://");
this.protocolSchema = url[0];
this.logHost = url[1];
this.email = creds[2];
}
return this;
}
public SnP getStudentAndParent() throws IOException, NotLoggedInErrorException {
if (null == getCookiesObject()) {
throw new NotLoggedInErrorException();
}
@ -58,6 +110,8 @@ public class Vulcan extends Api {
}
snp = createSnp(cookies, symbol, id);
snp.setLogHost(logHost);
snp.setProtocolSchema(protocolSchema);
snp.storeContextCookies();
@ -66,7 +120,7 @@ public class Vulcan extends Api {
return snp;
}
public StudentAndParent createSnp(Cookies cookies, String symbol, String id) {
public SnP createSnp(Cookies cookies, String symbol, String id) {
if (null == id) {
return new StudentAndParent(cookies, symbol);
}

View File

@ -8,15 +8,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class AttendanceStatistics {
private StudentAndParent snp;
private SnP snp;
private String attendancePageUrl = "Frekwencja.mvc";
public AttendanceStatistics(StudentAndParent snp) {
public AttendanceStatistics(SnP snp) {
this.snp = snp;
}

View File

@ -7,15 +7,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class AttendanceTable {
private StudentAndParent snp;
private SnP snp;
private String attendancePageUrl = "Frekwencja.mvc?data=";
public AttendanceTable(StudentAndParent snp) {
public AttendanceTable(SnP snp) {
this.snp = snp;
}

View File

@ -8,15 +8,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class ExamsWeek {
private final StudentAndParent snp;
private static final String EXAMS_PAGE_URL = "Sprawdziany.mvc/Terminarz?rodzajWidoku=2&data=";
public ExamsWeek(StudentAndParent snp) {
private final SnP snp;
public ExamsWeek(SnP snp) {
this.snp = snp;
}

View File

@ -15,17 +15,17 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.github.wulkanowy.api.Semester;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class GradesList {
private StudentAndParent snp = null;
private static final String GRADES_PAGE_URL = "Oceny/Wszystkie?details=2&okres=";
private SnP snp = null;
private List<Grade> grades = new ArrayList<>();
public GradesList(StudentAndParent snp) {
public GradesList(SnP snp) {
this.snp = snp;
}

View File

@ -8,15 +8,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class SubjectsList {
private StudentAndParent snp = null;
private static final String SUBJECTS_PAGE_URL = "Oceny/Wszystkie?details=1";
public SubjectsList(StudentAndParent snp) {
private SnP snp = null;
public SubjectsList(SnP snp) {
this.snp = snp;
}

View File

@ -6,34 +6,57 @@ import org.jsoup.parser.Parser;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.regex.Pattern;
import io.github.wulkanowy.api.Api;
import io.github.wulkanowy.api.Cookies;
public class Login extends Api {
private String loginPageUrl = "https://cufs.vulcan.net.pl/{symbol}/Account/LogOn" +
private static final String loginPageUrl = "{schema}://cufs.{host}/{symbol}/Account/LogOn" +
"?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" +
"https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" +
"https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx";
"{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" +
"{schema}%253a%252f%252fuonetplus.{host}%252f{symbol}%252fLoginEndpoint.aspx";
private String loginEndpointPageUrl =
"https://uonetplus.vulcan.net.pl/{symbol}/LoginEndpoint.aspx";
private static final String loginEndpointPageUrl =
"{schema}://uonetplus.{host}/{symbol}/LoginEndpoint.aspx";
private String protocolSchema = "https";
private String logHost = "vulcan.net.pl";
private String symbol = "Default";
public Login(Cookies cookies) {
this.cookies = cookies;
}
public void setProtocolSchema(String schema) {
this.protocolSchema = schema;
}
public void setLogHost(String hostname) {
this.logHost = hostname;
}
public String getLoginPageUrl() {
return loginPageUrl;
return loginPageUrl
.replace("{schema}", protocolSchema)
.replaceFirst(Pattern.quote("{host}"), logHost)
.replace("{host}", logHost.replace(":", "%253A"))
.replace("{symbol}", symbol);
}
public String getLoginEndpointPageUrl() {
return loginEndpointPageUrl;
return loginEndpointPageUrl
.replace("{schema}", protocolSchema)
.replace("{host}", logHost)
.replace("{symbol}", symbol);
}
public String login(String email, String password, String symbol)
throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException {
throws BadCredentialsException, LoginErrorException,
AccountPermissionException, IOException, VulcanOfflineException {
String certificate = sendCredentials(email, password, symbol);
return sendCertificate(certificate, symbol);
@ -41,9 +64,9 @@ public class Login extends Api {
public String sendCredentials(String email, String password, String symbol)
throws IOException, BadCredentialsException {
loginPageUrl = getLoginPageUrl().replace("{symbol}", symbol);
this.symbol = symbol;
Document html = postPageByUrl(loginPageUrl, new String[][]{
Document html = postPageByUrl(getLoginPageUrl(), new String[][]{
{"LoginName", email},
{"Password", password}
});
@ -56,13 +79,10 @@ public class Login extends Api {
}
public String sendCertificate(String certificate, String defaultSymbol)
throws IOException, LoginErrorException, AccountPermissionException {
String symbol = findSymbol(defaultSymbol, certificate);
throws IOException, LoginErrorException, AccountPermissionException, VulcanOfflineException {
this.symbol = findSymbol(defaultSymbol, certificate);
loginEndpointPageUrl = getLoginEndpointPageUrl()
.replace("{symbol}", symbol);
Document html = postPageByUrl(loginEndpointPageUrl, new String[][]{
Document html = postPageByUrl(getLoginEndpointPageUrl(), new String[][]{
{"wa", "wsignin1.0"},
{"wresult", certificate}
});
@ -71,11 +91,15 @@ public class Login extends Api {
throw new AccountPermissionException();
}
if (html.getElementsByTag("title").text().equals("Przerwa techniczna")) {
throw new VulcanOfflineException();
}
if (!html.select("title").text().equals("Uonet+")) {
throw new LoginErrorException();
}
return symbol;
return this.symbol;
}
public String findSymbol(String symbol, String certificate) {

View File

@ -0,0 +1,4 @@
package io.github.wulkanowy.api.login;
public class VulcanOfflineException extends Exception {
}

View File

@ -7,17 +7,17 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class AchievementsList {
private StudentAndParent snp = null;
private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie";
private SnP snp = null;
private List<String> achievements = new ArrayList<>();
private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie";
public AchievementsList(StudentAndParent snp) {
public AchievementsList(SnP snp) {
this.snp = snp;
}

View File

@ -7,17 +7,17 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class NotesList {
private StudentAndParent snp = null;
private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie";
private SnP snp = null;
private List<Note> notes = new ArrayList<>();
private static final String NOTES_PAGE_URL = "UwagiOsiagniecia.mvc/Wszystkie";
public NotesList(StudentAndParent snp) {
public NotesList(SnP snp) {
this.snp = snp;
}

View File

@ -4,15 +4,15 @@ import org.jsoup.nodes.Element;
import java.io.IOException;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class SchoolInfo {
private StudentAndParent snp = null;
private static final String SCHOOL_PAGE_URL = "Szkola.mvc/Nauczyciele";
public SchoolInfo(StudentAndParent snp) {
private SnP snp = null;
public SchoolInfo(SnP snp) {
this.snp = snp;
}

View File

@ -8,15 +8,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class TeachersInfo {
private StudentAndParent snp = null;
private static final String SCHOOL_PAGE_URL = "Szkola.mvc/Nauczyciele";
public TeachersInfo(StudentAndParent snp) {
private SnP snp = null;
public TeachersInfo(SnP snp) {
this.snp = snp;
}

View File

@ -12,15 +12,15 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class Timetable {
private StudentAndParent snp;
private static final String TIMETABLE_PAGE_URL = "Lekcja.mvc/PlanLekcji?data=";
public Timetable(StudentAndParent snp) {
private SnP snp;
public Timetable(SnP snp) {
this.snp = snp;
}

View File

@ -5,19 +5,19 @@ import org.jsoup.nodes.Element;
import java.io.IOException;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class BasicInformation {
private Document studentDataPageDocument;
private StudentAndParent snp;
private static final String STUDENT_DATA_PAGE_URL = "Uczen.mvc/DanePodstawowe";
private static final String CONTENT_QUERY = ".mainContainer > article";
public BasicInformation(StudentAndParent snp) {
private Document studentDataPageDocument;
private SnP snp;
public BasicInformation(SnP snp) {
this.snp = snp;
}

View File

@ -7,15 +7,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import io.github.wulkanowy.api.StudentAndParent;
import io.github.wulkanowy.api.SnP;
public class FamilyInformation {
private StudentAndParent snp;
private static final String STUDENT_DATA_PAGE_URL = "Uczen.mvc/DanePodstawowe";
public FamilyInformation(StudentAndParent snp) {
private SnP snp;
public FamilyInformation(SnP snp) {
this.snp = snp;
}

View File

@ -26,7 +26,8 @@ public class StudentAndParentTest {
Mockito.when(snp.getSnPPageDocument(Mockito.anyString())).thenReturn(gradesPageDocument);
Mockito.when(snp.getExtractedIdFromUrl(Mockito.anyString())).thenCallRealMethod();
Mockito.when(snp.getBaseUrl()).thenReturn("https://uonetplus-opiekun.vulcan.net.pl/{symbol}/{ID}/");
Mockito.when(snp.getLogHost()).thenReturn("vulcan.net.pl");
Mockito.when(snp.getBaseUrl()).thenReturn("https://uonetplus-opiekun.vulcan.net.pl/symbol/123456/");
Mockito.when(snp.getSymbol()).thenReturn("symbol");
Mockito.when(snp.getId()).thenReturn("123456");
Mockito.when(snp.getSemesters()).thenCallRealMethod();

View File

@ -8,8 +8,10 @@ import org.mockito.Mockito;
import io.github.wulkanowy.api.attendance.AttendanceStatistics;
import io.github.wulkanowy.api.attendance.AttendanceTable;
import io.github.wulkanowy.api.exams.ExamsWeek;
import io.github.wulkanowy.api.grades.GradesList;
import io.github.wulkanowy.api.grades.SubjectsList;
import io.github.wulkanowy.api.login.Login;
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
import io.github.wulkanowy.api.notes.AchievementsList;
import io.github.wulkanowy.api.notes.NotesList;
@ -31,6 +33,38 @@ public class VulcanTest extends Vulcan {
.thenReturn(snp);
}
@Test
public void setFullEndpointInfoTest() throws Exception {
SnP snp = new StudentAndParent(new Cookies(), "Default");
Login login = Mockito.mock(Login.class);
Vulcan vulcan = Mockito.mock(Vulcan.class);
Mockito.when(vulcan.login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenCallRealMethod();
Mockito.when(vulcan.login(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenCallRealMethod();
Mockito.when(vulcan.getLoginObject()).thenReturn(login);
Mockito.when(vulcan.createSnp(Mockito.any(Cookies.class), Mockito.anyString(), Mockito.anyString())).thenReturn(snp);
Mockito.when(vulcan.getCookiesObject()).thenCallRealMethod();
Mockito.when(vulcan.getStudentAndParent()).thenCallRealMethod();
Mockito.when(vulcan.setFullEndpointInfo(Mockito.anyString())).thenCallRealMethod();
Mockito.when(vulcan.getProtocolSchema()).thenCallRealMethod();
Mockito.when(vulcan.getLogHost()).thenCallRealMethod();
Mockito.when(vulcan.getEmail()).thenCallRealMethod();
vulcan.login("http://fakelog.net\\\\admin", "pass", "Default", "123");
Assert.assertEquals("http", vulcan.getProtocolSchema());
Assert.assertEquals("fakelog.net", vulcan.getLogHost());
Assert.assertEquals("admin", vulcan.getEmail());
}
@Test
public void getLoginObjectTest() throws Exception {
Mockito.when(vulcan.getLoginObject()).thenCallRealMethod();
Assert.assertThat(vulcan.getLoginObject(), CoreMatchers.instanceOf(Login.class));
}
@Test
public void getStudentAndParentTest() throws Exception {
Cookies cookies = new Cookies();
@ -47,7 +81,7 @@ public class VulcanTest extends Vulcan {
Mockito.when(vulcan.getStudentAndParent()).thenCallRealMethod();
StudentAndParent vulcanSnP = vulcan.getStudentAndParent();
SnP vulcanSnP = vulcan.getStudentAndParent();
Assert.assertEquals(snp, vulcanSnP);
Assert.assertEquals(vulcanSnP, vulcan.getStudentAndParent());
@ -93,6 +127,13 @@ public class VulcanTest extends Vulcan {
CoreMatchers.instanceOf(AttendanceStatistics.class));
}
@Test
public void getExamsListTest() throws Exception {
Mockito.when(vulcan.getExamsList()).thenCallRealMethod();
Assert.assertThat(vulcan.getExamsList(),
CoreMatchers.instanceOf(ExamsWeek.class));
}
@Test
public void getGradesListTest() throws Exception {
Mockito.when(vulcan.getGradesList()).thenCallRealMethod();

View File

@ -108,6 +108,14 @@ public class LoginTest {
login.sendCertificate(getFixtureAsString("cert.xml"), "demo123");
}
@Test(expected = VulcanOfflineException.class)
public void sendCertificateVulcanOfflineTest() throws Exception {
Login login = getSetUpLogin("PrzerwaTechniczna.html");
Mockito.when(login.findSymbol(Mockito.anyString(), Mockito.anyString())).thenCallRealMethod();
Mockito.when(login.sendCertificate(Mockito.anyString(), Mockito.anyString())).thenCallRealMethod();
login.sendCertificate(getFixtureAsString("cert.xml"), "demo123");
}
@Test
public void findSymbolInCertificateTest() throws Exception {
Login login = new Login(new Cookies());

View File

@ -90,6 +90,15 @@ public class TimetableTest extends StudentAndParentTestCase {
Assert.assertEquals("3", holidays.getWeekTable().getDay(3).getLesson(3).getNumber());
}
@Test
public void getLessonDayTest() throws Exception {
Assert.assertEquals("2017-06-19", std.getWeekTable().getDay(0).getLesson(1).getDate());
Assert.assertEquals("2017-06-23", std.getWeekTable().getDay(4).getLesson(4).getDate());
Assert.assertEquals("2017-06-20", full.getWeekTable().getDay(1).getLesson(6).getDate());
Assert.assertEquals("2017-06-22", full.getWeekTable().getDay(3).getLesson(3).getDate());
Assert.assertEquals("2017-08-02", holidays.getWeekTable().getDay(2).getLesson(8).getDate());
}
@Test
public void getLessonSubjectTest() throws Exception {
Assert.assertEquals("Historia", std.getWeekTable().getDay(0).getLesson(1).getSubject());

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Przerwa techniczna</title>
</head>
<body>
<div id="page_error">
<div id="info_error">
<div class="info_error">
<h1>Przerwa techniczna</h1>
<p>Aktualnie trwają prace konserwacyjne. Witryna będzie dostępna za kilka minut.</p>>
</div>
<div class="error_link">
<a href="http://www.vulcan.edu.pl">Przejdź do strony głównej</a>
<br>
<br>
<p>3</p>
</div>
</div>
</div>
</body>
</html>