├── base ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── drawable │ │ │ ├── offline.png │ │ │ └── online.png │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── ro │ │ └── polak │ │ └── webserver │ │ └── base │ │ ├── ConfigurationException.java │ │ ├── impl │ │ └── AndroidCliServerGui.java │ │ └── logic │ │ └── AssetUtil.java └── build.gradle ├── cli ├── .gitignore └── build.gradle ├── http ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ ├── ro │ │ │ └── polak │ │ │ │ └── http │ │ │ │ ├── exception │ │ │ │ ├── AccessDeniedException.java │ │ │ │ ├── MethodNotAllowedException.java │ │ │ │ ├── FilterInitializationException.java │ │ │ │ ├── ServletInitializationException.java │ │ │ │ ├── protocol │ │ │ │ │ ├── UnsupportedProtocolException.java │ │ │ │ │ ├── UriTooLongProtocolException.java │ │ │ │ │ ├── LengthRequiredException.java │ │ │ │ │ ├── PayloadTooLargeProtocolException.java │ │ │ │ │ ├── MalformedStatusLineException.java │ │ │ │ │ ├── StatusLineTooLongProtocolException.java │ │ │ │ │ ├── RangeNotSatisfiableProtocolException.java │ │ │ │ │ ├── MalformedOrUnsupportedMethodProtocolException.java │ │ │ │ │ └── ProtocolException.java │ │ │ │ ├── NotFoundException.java │ │ │ │ ├── ServletException.java │ │ │ │ └── UnexpectedSituationException.java │ │ │ │ ├── protocol │ │ │ │ ├── parser │ │ │ │ │ ├── MalformedInputException.java │ │ │ │ │ ├── Parser.java │ │ │ │ │ └── impl │ │ │ │ │ │ ├── QueryStringParser.java │ │ │ │ │ │ ├── CookieParser.java │ │ │ │ │ │ ├── RequestStatusParser.java │ │ │ │ │ │ └── RangeParser.java │ │ │ │ └── serializer │ │ │ │ │ ├── Serializer.java │ │ │ │ │ └── impl │ │ │ │ │ └── HeadersSerializer.java │ │ │ │ ├── servlet │ │ │ │ ├── ServletOutputStream.java │ │ │ │ ├── ServletConfig.java │ │ │ │ ├── FilterConfig.java │ │ │ │ ├── BasicAbstractFilter.java │ │ │ │ ├── Range.java │ │ │ │ ├── FilterChain.java │ │ │ │ ├── impl │ │ │ │ │ ├── FilterConfigImpl.java │ │ │ │ │ ├── ServletConfigImpl.java │ │ │ │ │ └── FilterChainImpl.java │ │ │ │ ├── ServletPrintWriter.java │ │ │ │ ├── Filter.java │ │ │ │ ├── Servlet.java │ │ │ │ ├── ServletContainer.java │ │ │ │ ├── HttpServlet.java │ │ │ │ ├── ChunkedPrintWriter.java │ │ │ │ ├── helper │ │ │ │ │ └── RangeHelper.java │ │ │ │ ├── UploadedFile.java │ │ │ │ ├── ServletContext.java │ │ │ │ └── factory │ │ │ │ │ └── HttpServletResponseImplFactory.java │ │ │ │ ├── errorhandler │ │ │ │ ├── HttpErrorHandlerResolver.java │ │ │ │ ├── impl │ │ │ │ │ ├── HttpError400Handler.java │ │ │ │ │ ├── HttpError414Handler.java │ │ │ │ │ ├── HttpError411Handler.java │ │ │ │ │ ├── HttpError413Handler.java │ │ │ │ │ ├── HttpError416Handler.java │ │ │ │ │ ├── HttpError503Handler.java │ │ │ │ │ ├── HttpError505Handler.java │ │ │ │ │ ├── HttpError403Handler.java │ │ │ │ │ ├── HttpError404Handler.java │ │ │ │ │ ├── HttpError405Handler.java │ │ │ │ │ └── HttpError500Handler.java │ │ │ │ ├── HttpErrorHandler.java │ │ │ │ └── AbstractPlainTextHttpErrorHandler.java │ │ │ │ ├── configuration │ │ │ │ ├── ServerConfigFactory.java │ │ │ │ ├── ServletMapping.java │ │ │ │ ├── FilterMapping.java │ │ │ │ ├── impl │ │ │ │ │ ├── ServletMappingImpl.java │ │ │ │ │ └── FilterMappingImpl.java │ │ │ │ ├── ServletMappingBuilder.java │ │ │ │ ├── FilterMappingBuilder.java │ │ │ │ ├── DeploymentDescriptorBuilder.java │ │ │ │ └── ServerConfig.java │ │ │ │ ├── gui │ │ │ │ └── ServerGui.java │ │ │ │ ├── utilities │ │ │ │ ├── DateProvider.java │ │ │ │ ├── DateUtilities.java │ │ │ │ └── IOUtilities.java │ │ │ │ ├── MimeTypeMapping.java │ │ │ │ ├── resource │ │ │ │ └── provider │ │ │ │ │ └── ResourceProvider.java │ │ │ │ ├── controller │ │ │ │ └── Controller.java │ │ │ │ ├── Loadable.java │ │ │ │ ├── PathHelper.java │ │ │ │ ├── session │ │ │ │ └── storage │ │ │ │ │ └── SessionStorage.java │ │ │ │ ├── RangePartHeader.java │ │ │ │ ├── MultipartHeadersPart.java │ │ │ │ ├── ServiceUnavailableHandler.java │ │ │ │ ├── RequestStatus.java │ │ │ │ └── impl │ │ │ │ └── ServletOutputStreamImpl.java │ │ │ └── example │ │ │ ├── NotFoundServlet.java │ │ │ ├── ForbiddenServlet.java │ │ │ ├── InternalServerErrorServlet.java │ │ │ ├── ForbiddenByFilterServlet.java │ │ │ ├── filter │ │ │ └── FakeSecuredAbstractFilter.java │ │ │ ├── ChunkedServlet.java │ │ │ ├── StreamingServlet.java │ │ │ ├── ChunkedWithDelayServlet.java │ │ │ ├── CookiesServlet.java │ │ │ ├── SessionServlet.java │ │ │ └── FileUploadServlet.java │ └── test │ │ └── java │ │ └── ro │ │ └── polak │ │ └── http │ │ ├── OsUtils.java │ │ ├── utilities │ │ ├── DateProviderTest.java │ │ ├── DateUtilitiesTest.java │ │ ├── StringUtilitiesTest.java │ │ ├── FileUtilitiesTest.java │ │ └── IOUtilitiesTest.java │ │ ├── servlet │ │ ├── impl │ │ │ ├── ServletConfigImplTest.java │ │ │ ├── HttpServletResponseImplIT.java │ │ │ └── HttpServletResponseImplTest.java │ │ ├── loader │ │ │ ├── SampleFilter.java │ │ │ └── SampleServlet.java │ │ ├── ServletPrintWriterTest.java │ │ ├── HttpServletTest.java │ │ ├── ChunkedPrintWriterTest.java │ │ └── CookieTest.java │ │ ├── controller │ │ └── impl │ │ │ └── LoggingUncaughtExceptionHandlerTest.java │ │ ├── errorhandler │ │ └── impl │ │ │ └── HttpErrorHandlerResolverImplTest.java │ │ ├── exception │ │ ├── ServletExceptionTest.java │ │ └── UnexpectedSituationExceptionTest.java │ │ ├── PathHelperTest.java │ │ ├── ServerRunnableTest.java │ │ ├── protocol │ │ ├── serializer │ │ │ └── impl │ │ │ │ └── HeadersSerializerTest.java │ │ └── parser │ │ │ └── impl │ │ │ ├── RequestStatusParserTest.java │ │ │ ├── CookieParserTest.java │ │ │ └── QueryStringParserTest.java │ │ ├── FileUtils.java │ │ ├── RequestBuilder.java │ │ ├── HeadersTest.java │ │ ├── impl │ │ └── MimeTypeMappingImplTest.java │ │ └── ServiceUnavailableHandlerTest.java └── build.gradle ├── screens ├── main.png ├── admin-login.png ├── admin-menu.png ├── admin-sms-inbox.png ├── admin-drive-access.png ├── error-500-stacktrace.png ├── admin-server-statistics.png └── command-line-interface.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── Procfile ├── app ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── online.png │ │ │ └── offline.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── styles.xml │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ │ ├── menu │ │ │ └── menu_main.xml │ │ └── values-w820dp │ │ │ └── dimens.xml │ │ ├── assets │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── assets │ │ │ │ ├── img │ │ │ │ ├── gif.png │ │ │ │ ├── home.png │ │ │ │ ├── jpg.png │ │ │ │ ├── pdf.png │ │ │ │ ├── png.png │ │ │ │ ├── zip.png │ │ │ │ ├── folder.png │ │ │ │ └── default.png │ │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ │ └── css │ │ │ │ └── styles.css │ │ └── conf │ │ │ └── httpd.properties │ │ ├── java │ │ ├── admin │ │ │ ├── LogoutServlet.java │ │ │ ├── logic │ │ │ │ ├── FileIconMapper.java │ │ │ │ └── AccessControl.java │ │ │ ├── filter │ │ │ │ ├── LogoutFilter.java │ │ │ │ └── SecurityFilter.java │ │ │ ├── IndexServlet.java │ │ │ └── ServerStatsServlet.java │ │ ├── ro │ │ │ └── polak │ │ │ │ └── webserver │ │ │ │ └── MainService.java │ │ └── api │ │ │ └── logic │ │ │ ├── MessageDTOMapper.java │ │ │ └── APIResponse.java │ │ └── AndroidManifest.xml └── build.gradle ├── settings.gradle ├── .gitignore ├── installsdk.sh ├── .gitattributes └── .github └── workflows ├── on_pr.yaml └── on_master.yaml /base/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /http/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /screens/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/main.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | android.enableJetifier=true 3 | android.useAndroidX=true -------------------------------------------------------------------------------- /screens/admin-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/admin-login.png -------------------------------------------------------------------------------- /screens/admin-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/admin-menu.png -------------------------------------------------------------------------------- /screens/admin-sms-inbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/admin-sms-inbox.png -------------------------------------------------------------------------------- /screens/admin-drive-access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/admin-drive-access.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /screens/error-500-stacktrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/error-500-stacktrace.png -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: mkdir ./httpd/ && echo "server.port=$PORT" >> ./httpd/httpd.properties && java $JAVA_OPTS -jar ./cli/build/libs/cli-all.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/drawable/online.png -------------------------------------------------------------------------------- /screens/admin-server-statistics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/admin-server-statistics.png -------------------------------------------------------------------------------- /screens/command-line-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/screens/command-line-interface.png -------------------------------------------------------------------------------- /app/src/main/assets/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/favicon.ico -------------------------------------------------------------------------------- /app/src/main/res/drawable/offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/drawable/offline.png -------------------------------------------------------------------------------- /base/src/main/res/drawable/offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/base/src/main/res/drawable/offline.png -------------------------------------------------------------------------------- /base/src/main/res/drawable/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/base/src/main/res/drawable/online.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':cli', ':http' 2 | 3 | if (!settings.hasProperty("skipAndroidBuild")) { 4 | include ':base', ':app' 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/gif.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/home.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/jpg.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/pdf.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/png.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/zip.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/folder.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/img/default.png -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrpolak/android-http-server/HEAD/app/src/main/assets/public/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/assets/public/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | } 4 | .form-login, .box-logout { 5 | max-width: 330px; 6 | padding: 15px; 7 | margin: 0 auto; 8 | } 9 | .form-login input { 10 | margin-bottom: 10px; 11 | } 12 | .container { 13 | max-width: 700px; 14 | } 15 | .bg-info { 16 | padding: 10px; 17 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTTPServer 4 | HTTP daemon started 5 | HTTP daemon stopped 6 | HTTP daemon 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/AccessDeniedException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Access denied exception. 12 | */ 13 | public class AccessDeniedException extends RuntimeException { 14 | } 15 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/MethodNotAllowedException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Method not allowed exception. 12 | */ 13 | public class MethodNotAllowedException extends RuntimeException { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/assets/conf/httpd.properties: -------------------------------------------------------------------------------- 1 | # server.port=8080 2 | server.static.path=./www/ 3 | server.static.directoryIndex=index.html,index.htm,Index 4 | server.mimeType.filePath=mime.type 5 | server.mimeType.defaultMimeType=text/plain 6 | server.maxThreads=10 7 | server.keepAlive.enabled=false 8 | 9 | #server.errorDocument.404=./errors/404.html 10 | #server.errorDocument.403=./errors/403.html 11 | 12 | admin.login=admin 13 | admin.password=1234 14 | admin.driveAccess.enabled=true 15 | 16 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/OsUtils.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import java.util.Locale; 4 | 5 | // CHECKSTYLE.OFF: JavadocType 6 | public final class OsUtils { 7 | 8 | private OsUtils() { 9 | } 10 | 11 | /** 12 | * Tells whether the current runtime is Windows OS. 13 | * 14 | * @return 15 | */ 16 | public static boolean isWindows() { 17 | return System.getProperty("os.name", "unknown").toLowerCase(Locale.ROOT).contains("win"); 18 | } 19 | } 20 | // CHECKSTYLE.ON: JavadocType 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | *.iml 32 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 33 | .gradle 34 | build/ -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/MalformedInputException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.parser; 9 | 10 | /** 11 | * Malformed input exception. 12 | */ 13 | public class MalformedInputException extends Exception { 14 | 15 | public MalformedInputException(final String s) { 16 | super(s); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ServletOutputStream.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.io.OutputStream; 11 | 12 | /** 13 | * Servlet output stream. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 201610 17 | */ 18 | public abstract class ServletOutputStream extends OutputStream { 19 | } 20 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/FilterInitializationException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Filter initialization exception. 12 | */ 13 | public class FilterInitializationException extends Exception { 14 | 15 | public FilterInitializationException(final Throwable throwable) { 16 | super(throwable); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/ServletInitializationException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Servlet initialization exception. 12 | */ 13 | public class ServletInitializationException extends Exception { 14 | 15 | public ServletInitializationException(final Throwable throwable) { 16 | super(throwable); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/UnsupportedProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Unsupported protocol. 12 | */ 13 | public class UnsupportedProtocolException extends ProtocolException { 14 | 15 | public UnsupportedProtocolException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/UriTooLongProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * URI exceeds URI limit. 12 | */ 13 | public class UriTooLongProtocolException extends ProtocolException { 14 | 15 | public UriTooLongProtocolException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Not found exception. 12 | */ 13 | public class NotFoundException extends RuntimeException { 14 | 15 | public NotFoundException() { 16 | } 17 | 18 | public NotFoundException(final String message) { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/LengthRequiredException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Length required exception. 12 | */ 13 | public class LengthRequiredException extends ProtocolException { 14 | public LengthRequiredException() { 15 | super("Length header is required for POST requests."); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/PayloadTooLargeProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Payload too large. 12 | */ 13 | public class PayloadTooLargeProtocolException extends ProtocolException { 14 | 15 | public PayloadTooLargeProtocolException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/MalformedStatusLineException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Malformed status line exception. 12 | */ 13 | public class MalformedStatusLineException extends ProtocolException { 14 | 15 | public MalformedStatusLineException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/HttpErrorHandlerResolver.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler; 9 | 10 | /** 11 | * Resolves HttpErrorHandler for given runtime exception. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201708 15 | */ 16 | public interface HttpErrorHandlerResolver { 17 | HttpErrorHandler getHandler(RuntimeException e); 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/StatusLineTooLongProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Status line exceeds its limit. 12 | */ 13 | public class StatusLineTooLongProtocolException extends ProtocolException { 14 | 15 | public StatusLineTooLongProtocolException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ServletConfig.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | /** 11 | * Servlet config. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201610 15 | */ 16 | public interface ServletConfig { 17 | 18 | /** 19 | * Returns servlet context. 20 | * 21 | * @return 22 | */ 23 | ServletContext getServletContext(); 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/FilterConfig.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | /** 11 | * Filter config. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201803 15 | */ 16 | public interface FilterConfig { 17 | 18 | /** 19 | * Returns associated servlet context. 20 | * 21 | * @return 22 | */ 23 | ServletContext getServletContext(); 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/RangeNotSatisfiableProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Exception thrown when range is not satisfiable and can not be served. 12 | */ 13 | public class RangeNotSatisfiableProtocolException extends ProtocolException { 14 | 15 | public RangeNotSatisfiableProtocolException() { 16 | super("Range not satisfiable"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/ServerConfigFactory.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | /** 11 | * Server config factory. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201611 15 | */ 16 | public interface ServerConfigFactory { 17 | 18 | /** 19 | * Produces and returns server config. 20 | * 21 | * @return 22 | */ 23 | ServerConfig getServerConfig(); 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/MalformedOrUnsupportedMethodProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Method too long or unknown (not supported). 12 | */ 13 | public class MalformedOrUnsupportedMethodProtocolException extends ProtocolException { 14 | 15 | public MalformedOrUnsupportedMethodProtocolException(final String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/gui/ServerGui.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package ro.polak.http.gui; 9 | 10 | /** 11 | * Defines methods that should be implemented by the server runner GUI (CLI, Swing, Android..). 12 | */ 13 | public interface ServerGui { 14 | 15 | /** 16 | * GUI method called by controller on stop. 17 | */ 18 | void stop(); 19 | 20 | /** 21 | * GUI method called by controller on start. 22 | */ 23 | void start(); 24 | } 25 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/utilities/DateProviderTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.utilities; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Date; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.Matchers.is; 9 | import static org.hamcrest.Matchers.lessThanOrEqualTo; 10 | 11 | // CHECKSTYLE.OFF: JavadocType 12 | public class DateProviderTest { 13 | 14 | @Test 15 | public void shouldReturnAValidDate() throws Exception { 16 | DateProvider dateProvider = new DateProvider(); 17 | assertThat(dateProvider.now().getTime(), is(lessThanOrEqualTo(new Date().getTime()))); 18 | } 19 | } 20 | // CHECKSTYLE.ON: JavadocType 21 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/protocol/ProtocolException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception.protocol; 9 | 10 | /** 11 | * Generic protocol exception. 12 | */ 13 | public class ProtocolException extends RuntimeException { 14 | 15 | public ProtocolException(final String message) { 16 | super(message); 17 | } 18 | 19 | public ProtocolException(final String s, final Throwable throwable) { 20 | super(s, throwable); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/utilities/DateProvider.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | package ro.polak.http.utilities; 8 | 9 | import java.util.Date; 10 | 11 | /** 12 | * Date provider. Makes testing deterministic. 13 | * 14 | * @author Piotr Polak piotr [at] polak [dot] ro 15 | * @since 201710 16 | */ 17 | public class DateProvider { 18 | 19 | /** 20 | * Returns the current date. 21 | * 22 | * @return 23 | */ 24 | public Date now() { 25 | return new Date(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/MimeTypeMapping.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http; 9 | 10 | /** 11 | * Mime type mapping. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 200802 15 | */ 16 | public interface MimeTypeMapping { 17 | 18 | /** 19 | * Returns mime type for specified extension. 20 | * 21 | * @param extension extension 22 | * @return mime type for specified extension 23 | */ 24 | String getMimeTypeByExtension(String extension); 25 | } 26 | -------------------------------------------------------------------------------- /base/src/main/java/ro/polak/webserver/base/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.webserver.base; 9 | 10 | /** 11 | * Represents a configuration exception. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201710 15 | */ 16 | public class ConfigurationException extends RuntimeException { 17 | 18 | public ConfigurationException(final String s) { 19 | super(s); 20 | } 21 | 22 | public ConfigurationException(final Throwable e) { 23 | super(e); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/ServletException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Default servlet exception. 12 | */ 13 | public class ServletException extends Exception { 14 | 15 | public ServletException(final String s) { 16 | super(s); 17 | } 18 | 19 | public ServletException(final String s, final Throwable throwable) { 20 | super(s, throwable); 21 | } 22 | 23 | public ServletException(final Throwable throwable) { 24 | super(throwable); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /installsdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -P/tmp && \ 4 | unzip /tmp/commandlinetools-linux-6858069_latest.zip -d ~/android-sdk && \ 5 | unlink /tmp/commandlinetools-linux-6858069_latest.zip && \ 6 | export ANDROID_HOME=~/android-sdk && \ 7 | mv $ANDROID_HOME/cmdline-tools/ $ANDROID_HOME/latest && \ 8 | mkdir $ANDROID_HOME/cmdline-tools/ && \ 9 | mv $ANDROID_HOME/latest $ANDROID_HOME/cmdline-tools/latest && \ 10 | mkdir -p ~/.android && \ 11 | touch ~/.android/repositories.cfg && \ 12 | yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-26" && \ 13 | yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses 14 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/BasicAbstractFilter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2019-2020 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | /** 11 | * Basic filter, does not require initialization. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 202001 15 | */ 16 | public abstract class BasicAbstractFilter implements Filter { 17 | 18 | /** 19 | * Does nothing. 20 | * 21 | * @param filterConfig 22 | */ 23 | @Override 24 | public void init(final FilterConfig filterConfig) { 25 | // Do Nothing 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.serializer; 9 | 10 | /** 11 | * Generic serializer interface. 12 | * 13 | * @param the type of the input. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 201611 17 | */ 18 | public interface Serializer { 19 | 20 | /** 21 | * Produced serialized string representation of the input attribute. 22 | * 23 | * @param input 24 | * @return 25 | */ 26 | String serialize(T input); 27 | } 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/Parser.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.parser; 9 | 10 | /** 11 | * Generic parser interface. 12 | * 13 | * @param the parse output type. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 201611 17 | */ 18 | public interface Parser { 19 | 20 | /** 21 | * Parses input string into the destination format. 22 | * 23 | * @param input 24 | * @return 25 | * @throws MalformedInputException 26 | */ 27 | T parse(String input) throws MalformedInputException; 28 | } 29 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/Range.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | package ro.polak.http.servlet; 8 | 9 | /** 10 | * Represents HTTP range. 11 | * 12 | * @author Piotr Polak piotr [at] polak [dot] ro 13 | * @since 201702 14 | */ 15 | public final class Range { 16 | 17 | private long from; 18 | private long to; 19 | 20 | public Range(final long from, final long to) { 21 | this.from = from; 22 | this.to = to; 23 | } 24 | 25 | public long getFrom() { 26 | return from; 27 | } 28 | 29 | public long getTo() { 30 | return to; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/resource/provider/ResourceProvider.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package ro.polak.http.resource.provider; 9 | 10 | import ro.polak.http.Loadable; 11 | 12 | /** 13 | * Interface used for loading certain types of HTTP resources. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 201610 17 | */ 18 | public interface ResourceProvider extends Loadable { 19 | 20 | /** 21 | * Tells whether this resource provider can load resource for given path. 22 | * 23 | * @param path 24 | * @return 25 | */ 26 | boolean canLoad(String path); 27 | } 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError400Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 400 Bad Request. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError400Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError400Handler() { 22 | super(HttpServletResponse.STATUS_BAD_REQUEST, "Error 400 - Bad Request"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/HttpErrorHandler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.servlet.HttpServletResponse; 13 | 14 | /** 15 | * IHTTPError interface defining serve method. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201509 19 | */ 20 | public interface HttpErrorHandler { 21 | 22 | /** 23 | * Serves the error page. 24 | * 25 | * @param response 26 | * @throws IOException 27 | */ 28 | void serve(HttpServletResponse response) throws IOException; 29 | } 30 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError414Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 414 URI Too Long. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError414Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError414Handler() { 22 | super(HttpServletResponse.STATUS_URI_TOO_LONG, "Error 414 - URI Too Long"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/exception/UnexpectedSituationException.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.exception; 9 | 10 | /** 11 | * Generic unrecoverable runtime exception. 12 | */ 13 | public class UnexpectedSituationException extends RuntimeException { 14 | 15 | public UnexpectedSituationException(final String message) { 16 | super(message); 17 | } 18 | 19 | public UnexpectedSituationException(final String message, final Throwable e) { 20 | super(message, e); 21 | } 22 | 23 | public UnexpectedSituationException(final Throwable e) { 24 | super(e); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError411Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 411 Length required. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError411Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError411Handler() { 22 | super(HttpServletResponse.STATUS_LENGTH_REQUIRED, "Error 411 - Length Required"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/impl/ServletConfigImplTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.nullValue; 8 | import static org.hamcrest.core.IsNot.not; 9 | import static org.mockito.Mockito.mock; 10 | 11 | // CHECKSTYLE.OFF: JavadocType 12 | public class ServletConfigImplTest { 13 | 14 | @Test 15 | public void shouldWorkSetterAndGetter() { 16 | ServletContextImpl servletContextImpl = mock(ServletContextImpl.class); 17 | ServletConfigImpl servletConfigImpl = new ServletConfigImpl(servletContextImpl); 18 | assertThat(servletConfigImpl.getServletContext(), is(not(nullValue()))); 19 | } 20 | } 21 | // CHECKSTYLE.ON: JavadocType 22 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError413Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 413 Request Entity Too Large. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError413Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError413Handler() { 22 | super(HttpServletResponse.REQUEST_ENTITY_TOO_LARGE, "Error 413 Request Entity Too Large"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError416Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 416 Requested Range Not Satisfiable. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201701 18 | */ 19 | public class HttpError416Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError416Handler() { 22 | super(HttpServletResponse.STATUS_RANGE_NOT_SATISFIABLE, "Error 416 Range Not Satisfiable"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError503Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 503 Service Unavailable HTTP error handler. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError503Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError503Handler() { 22 | super(HttpServletResponse.STATUS_SERVICE_UNAVAILABLE, "Error 503 - Service Unavailable"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError505Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractPlainTextHttpErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 505 HTTP Version Not Supported error handler. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError505Handler extends AbstractPlainTextHttpErrorHandler { 20 | 21 | public HttpError505Handler() { 22 | super(HttpServletResponse.HTTP_VERSION_NOT_SUPPORTED, "Error 505 - HTTP Version Not Supported"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/admin/LogoutServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package admin; 9 | 10 | import ro.polak.http.exception.ServletException; 11 | import ro.polak.http.servlet.HttpServlet; 12 | import ro.polak.http.servlet.HttpServletRequest; 13 | import ro.polak.http.servlet.HttpServletResponse; 14 | 15 | /** 16 | * Logout servlet. The logout procedure is captured in a LogoutFilter. 17 | */ 18 | public class LogoutServlet extends HttpServlet { 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 25 | // Logout using filter 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/utilities/DateUtilitiesTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.utilities; 2 | 3 | import org.hamcrest.CoreMatchers; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Date; 7 | 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.core.Is.is; 10 | import static ro.polak.http.ExtraMarchers.utilityClass; 11 | 12 | // CHECKSTYLE.OFF: JavadocType 13 | public class DateUtilitiesTest { 14 | 15 | @Test 16 | public void shouldNotBeInstantiableFinalClass() { 17 | assertThat(DateUtilities.class, CoreMatchers.is(utilityClass())); 18 | } 19 | 20 | // CHECKSTYLE.OFF: MagicNumber 21 | @Test 22 | public void shouldFormatDate() { 23 | assertThat(DateUtilities.dateFormat(new Date(1520881821937L)), is("Mon, 12 Mar 2018 19:10:21 GMT")); 24 | } 25 | // CHECKSTYLE.ON: MagicNumber 26 | } 27 | // CHECKSTYLE.ON: JavadocType 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/ServletMapping.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.servlet.HttpServlet; 13 | 14 | /** 15 | * Represents Servlet to URL patter mapping. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201803 19 | */ 20 | public interface ServletMapping { 21 | 22 | /** 23 | * Returns registration URL pattern. 24 | * 25 | * @return 26 | */ 27 | Pattern getUrlPattern(); 28 | 29 | /** 30 | * Returns mapped servlet class. 31 | * 32 | * @return 33 | */ 34 | Class getServletClass(); 35 | } 36 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError403Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractHtmlErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 403 Forbidden HTTP error handler. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError403Handler extends AbstractHtmlErrorHandler { 20 | 21 | public HttpError403Handler(final String errorDocumentPath) { 22 | super(HttpServletResponse.STATUS_ACCESS_DENIED, "Error 403 - Forbidden", "

Access Denied.

", 23 | errorDocumentPath); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /http/src/main/java/example/NotFoundServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import ro.polak.http.exception.NotFoundException; 11 | import ro.polak.http.exception.ServletException; 12 | import ro.polak.http.servlet.HttpServlet; 13 | import ro.polak.http.servlet.HttpServletRequest; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | 16 | /** 17 | * Not found page example page. 18 | */ 19 | public class NotFoundServlet extends HttpServlet { 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 26 | throw new NotFoundException(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /http/src/main/java/example/ForbiddenServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import ro.polak.http.exception.AccessDeniedException; 11 | import ro.polak.http.exception.ServletException; 12 | import ro.polak.http.servlet.HttpServlet; 13 | import ro.polak.http.servlet.HttpServletRequest; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | 16 | /** 17 | * Forbidden page example. 18 | */ 19 | public class ForbiddenServlet extends HttpServlet { 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 26 | throw new AccessDeniedException(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /http/src/main/java/example/InternalServerErrorServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import ro.polak.http.exception.ServletException; 11 | import ro.polak.http.servlet.HttpServlet; 12 | import ro.polak.http.servlet.HttpServletRequest; 13 | import ro.polak.http.servlet.HttpServletResponse; 14 | 15 | /** 16 | * Internal server error page example page. 17 | */ 18 | public class InternalServerErrorServlet extends HttpServlet { 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 25 | throw new ServletException("Something bad has just happened"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/controller/Controller.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package ro.polak.http.controller; 9 | 10 | import ro.polak.http.WebServer; 11 | 12 | /** 13 | * Defines methods that must be implemented by the server controller. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 201012 17 | */ 18 | public interface Controller { 19 | 20 | /** 21 | * Starts the server logic. 22 | */ 23 | void start() throws IllegalStateException; 24 | 25 | /** 26 | * Stops the server logic. 27 | */ 28 | void stop() throws IllegalStateException; 29 | 30 | /** 31 | * Returns web server instance. 32 | * 33 | * @return 34 | */ 35 | WebServer getWebServer(); 36 | } 37 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/FilterChain.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | 14 | /** 15 | * Filter chain interface easing running filters. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201803 19 | */ 20 | public interface FilterChain { 21 | 22 | /** 23 | * Filters use the FilterChain to invoke the next filter in the chain. 24 | * 25 | * @param request 26 | * @param response 27 | * @throws IOException 28 | * @throws ServletException 29 | */ 30 | void doFilter(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; 31 | } 32 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError404Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import ro.polak.http.errorhandler.AbstractHtmlErrorHandler; 11 | import ro.polak.http.servlet.HttpServletResponse; 12 | 13 | /** 14 | * 404 File Not Found HTTP error handler. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201509 18 | */ 19 | public class HttpError404Handler extends AbstractHtmlErrorHandler { 20 | 21 | public HttpError404Handler(final String errorDocumentPath) { 22 | super(HttpServletResponse.STATUS_NOT_FOUND, "Error 404 - File Not Found", 23 | "

The server has not found anything matching the specified URL.

", 24 | errorDocumentPath); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/controller/impl/LoggingUncaughtExceptionHandlerTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.controller.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.times; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.when; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class LoggingUncaughtExceptionHandlerTest { 12 | 13 | @Test 14 | public void shouldLogException() { 15 | Thread.UncaughtExceptionHandler handler 16 | = new ControllerImpl.LoggingUncaughtExceptionHandler(); 17 | 18 | Throwable throwable = mock(Throwable.class); 19 | when(throwable.getStackTrace()).thenReturn(new StackTraceElement[]{new StackTraceElement("X", "X", "X", 1)}); 20 | handler.uncaughtException(Thread.currentThread(), throwable); 21 | 22 | verify(throwable, times(1)).getStackTrace(); 23 | } 24 | } 25 | // CHECKSTYLE.ON: JavadocType 26 | -------------------------------------------------------------------------------- /http/src/main/java/example/ForbiddenByFilterServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.io.PrintWriter; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | import ro.polak.http.servlet.HttpServlet; 14 | import ro.polak.http.servlet.HttpServletRequest; 15 | import ro.polak.http.servlet.HttpServletResponse; 16 | 17 | /** 18 | * Forbidden by filter page example. 19 | */ 20 | public class ForbiddenByFilterServlet extends HttpServlet { 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | @Override 26 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 27 | PrintWriter printWriter = response.getWriter(); 28 | printWriter.println("Hello World!"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /base/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | buildscript { 4 | dependencies { 5 | classpath 'com.android.tools.build:gradle:4.2.0' 6 | 7 | // NOTE: Do not place your application dependencies here; they belong 8 | // in the individual module build.gradle files 9 | } 10 | repositories { 11 | jcenter() 12 | google() 13 | } 14 | } 15 | 16 | android { 17 | compileSdkVersion 29 18 | buildToolsVersion '30.0.2' 19 | defaultConfig { 20 | minSdkVersion 19 21 | targetSdkVersion 29 22 | versionCode 1 23 | versionName "1.0" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | } 29 | } 30 | productFlavors { 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | implementation 'androidx.appcompat:appcompat:1.0.0' 37 | implementation project(path: ':http') 38 | implementation project(path: ':cli') 39 | } 40 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/FilterMapping.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.servlet.Filter; 13 | 14 | /** 15 | * Represents Filter to URL patter mapping. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201803 19 | */ 20 | public interface FilterMapping { 21 | 22 | /** 23 | * Returns registration URL pattern. 24 | * 25 | * @return 26 | */ 27 | Pattern getUrlPattern(); 28 | 29 | /** 30 | * Returns registration excluded URL pattern. 31 | * 32 | * @return 33 | */ 34 | Pattern getUrlExcludePattern(); 35 | 36 | /** 37 | * Returns mapped servlet class. 38 | * 39 | * @return 40 | */ 41 | Class getFilterClass(); 42 | } 43 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/loader/SampleFilter.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet.loader; 2 | 3 | import ro.polak.http.servlet.Filter; 4 | import ro.polak.http.servlet.FilterChain; 5 | import ro.polak.http.servlet.FilterConfig; 6 | import ro.polak.http.servlet.HttpServletRequest; 7 | import ro.polak.http.servlet.HttpServletResponse; 8 | 9 | // CHECKSTYLE.OFF: DesignForExtension JavadocType 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class SampleFilter implements Filter { 12 | 13 | private FilterConfig filterConfig; 14 | 15 | @Override 16 | public void init(final FilterConfig filterConfig) { 17 | this.filterConfig = filterConfig; 18 | } 19 | 20 | @Override 21 | public void doFilter(final HttpServletRequest request, final HttpServletResponse response, 22 | final FilterChain filterChain) { 23 | // Do Nothing 24 | } 25 | 26 | public FilterConfig getFilterConfig() { 27 | return filterConfig; 28 | } 29 | } 30 | // CHECKSTYLE.ON: JavadocType 31 | // CHECKSTYLE.ON: DesignForExtension 32 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/ServletPrintWriterTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.function.Executable; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.fail; 8 | import static org.mockito.Mockito.mock; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class ServletPrintWriterTest { 12 | 13 | @Test 14 | public void shouldNotAllowToWriteEndMoreThanOnce() { 15 | final ServletPrintWriter servletPrintWriter = new ServletPrintWriter(mock(ServletOutputStream.class)); 16 | try { 17 | servletPrintWriter.writeEnd(); 18 | } catch (Throwable e) { 19 | fail("Should not throw any exception here"); 20 | } 21 | 22 | assertThrows(IllegalStateException.class, new Executable() { 23 | @Override 24 | public void execute() { 25 | servletPrintWriter.writeEnd(); 26 | } 27 | }); 28 | } 29 | } 30 | // CHECKSTYLE.ON: JavadocType 31 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/impl/FilterConfigImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet.impl; 9 | 10 | import ro.polak.http.servlet.FilterConfig; 11 | import ro.polak.http.servlet.ServletContext; 12 | 13 | /** 14 | * Default config. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201803 18 | */ 19 | public class FilterConfigImpl implements FilterConfig { 20 | 21 | private final ServletContext servletContext; 22 | 23 | /** 24 | * Default constructor. 25 | * 26 | * @param servletContext 27 | */ 28 | public FilterConfigImpl(final ServletContext servletContext) { 29 | this.servletContext = servletContext; 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public ServletContext getServletContext() { 37 | return servletContext; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | # See https://help.github.com/articles/dealing-with-line-endings/ 3 | 4 | * text=auto 5 | 6 | # Convert crlf => lf for the specified files 7 | 8 | # source code 9 | *.java text eol=lf 10 | *.css text eol=lf 11 | *.sass text eol=lf 12 | *.scss text eol=lf 13 | *.js text eol=lf 14 | *.json text eol=lf 15 | *.htm text eol=lf 16 | *.html text eol=lf 17 | *.xml text eol=lf 18 | *.iml text eol=lf 19 | *.svg text eol=lf 20 | *.txt text eol=lf 21 | *.ini text eol=lf 22 | *.inc text eol=lf 23 | *.pl text eol=lf 24 | *.rb text eol=lf 25 | *.scm text eol=lf 26 | *.sql text eol=lf 27 | *.sh text eol=lf 28 | 29 | # git config 30 | .gitattributes text eol=lf 31 | .gitignore text eol=lf 32 | .gitconfig text eol=lf 33 | 34 | # misc config 35 | *.yaml text eol=lf 36 | *.yml text eol=lf 37 | *.gradle text eol=lf 38 | *.properties text eol=lf 39 | 40 | # Documentation 41 | *.md text eol=lf 42 | LICENSE text eol=lf 43 | AUTHORS text eol=lf 44 | 45 | # (binary is a macro for -text -diff), leave them unouched 46 | *.png binary 47 | *.jpg binary 48 | *.jar -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | buildscript { 4 | dependencies { 5 | classpath 'com.android.tools.build:gradle:4.2.0' 6 | 7 | // NOTE: Do not place your application dependencies here; they belong 8 | // in the individual module build.gradle files 9 | } 10 | repositories { 11 | jcenter() 12 | google() 13 | } 14 | } 15 | 16 | android { 17 | compileSdkVersion 29 18 | buildToolsVersion '30.0.2' 19 | defaultConfig { 20 | applicationId "ro.polak.webserver" 21 | minSdkVersion 19 22 | targetSdkVersion 29 23 | versionCode 1 24 | versionName "1.0" 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | } 30 | } 31 | productFlavors { 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | implementation 'androidx.appcompat:appcompat:1.0.0' 38 | implementation project(path: ':http') 39 | implementation project(path: ':cli') 40 | implementation project(path: ':base') 41 | } 42 | -------------------------------------------------------------------------------- /cli/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | compileJava { 5 | sourceCompatibility = 1.7 6 | } 7 | 8 | compileTestJava { 9 | sourceCompatibility = 1.8 10 | } 11 | 12 | jar { 13 | manifest { 14 | attributes 'Main-Class': 'ro.polak.http.cli.DefaultCliServerGui' 15 | } 16 | } 17 | 18 | task fatJar(type: Jar) { 19 | manifest { 20 | attributes 'Implementation-Title': 'StandaloneHTTP server', 21 | 'Main-Class': 'ro.polak.http.cli.DefaultCliServerGui' 22 | } 23 | baseName = project.name + '-all' 24 | from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } 25 | with jar 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation project(path: ':http') 31 | } 32 | 33 | 34 | task(bootRun, dependsOn: fatJar) { 35 | doLast { 36 | print "$buildDir/libs/cli.jar" 37 | javaexec { 38 | main = "-jar" 39 | args = [ 40 | "$buildDir/libs/cli-all.jar" 41 | ] 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/impl/ServletConfigImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet.impl; 9 | 10 | import ro.polak.http.servlet.ServletConfig; 11 | import ro.polak.http.servlet.ServletContext; 12 | 13 | /** 14 | * Servlet config implementation. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201610 18 | */ 19 | public class ServletConfigImpl implements ServletConfig { 20 | 21 | private final ServletContext servletContext; 22 | 23 | /** 24 | * Default constructor. 25 | * 26 | * @param servletContext 27 | */ 28 | public ServletConfigImpl(final ServletContext servletContext) { 29 | this.servletContext = servletContext; 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public ServletContext getServletContext() { 37 | return servletContext; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/errorhandler/impl/HttpErrorHandlerResolverImplTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.errorhandler.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import ro.polak.http.configuration.ServerConfig; 6 | import ro.polak.http.errorhandler.HttpErrorHandler; 7 | import ro.polak.http.errorhandler.HttpErrorHandlerResolver; 8 | import ro.polak.http.exception.NotFoundException; 9 | 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.Matchers.instanceOf; 12 | import static org.hamcrest.Matchers.is; 13 | 14 | // CHECKSTYLE.OFF: JavadocType 15 | public class HttpErrorHandlerResolverImplTest { 16 | 17 | @Test 18 | public void shouldCaptureIntermediateExceptions() { 19 | ServerConfig serverConfig = null; // WIll throw NPE 20 | HttpErrorHandlerResolver httpErrorHandlerResolver = new HttpErrorHandlerResolverImpl(serverConfig); 21 | HttpErrorHandler handler = httpErrorHandlerResolver.getHandler(new NotFoundException()); 22 | assertThat(handler, is(instanceOf(HttpError500Handler.class))); 23 | } 24 | } 25 | // CHECKSTYLE.ON: JavadocType 26 | -------------------------------------------------------------------------------- /http/src/main/java/example/filter/FakeSecuredAbstractFilter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package example.filter; 9 | 10 | import ro.polak.http.exception.AccessDeniedException; 11 | import ro.polak.http.servlet.BasicAbstractFilter; 12 | import ro.polak.http.servlet.FilterChain; 13 | import ro.polak.http.servlet.HttpServletRequest; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | 16 | /** 17 | * Always throws AccessDeniedException. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201803 21 | */ 22 | public class FakeSecuredAbstractFilter extends BasicAbstractFilter { 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public void doFilter(final HttpServletRequest request, 29 | final HttpServletResponse response, 30 | final FilterChain filterChain) { 31 | throw new AccessDeniedException(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/exception/ServletExceptionTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.core.Is.is; 7 | 8 | // CHECKSTYLE.OFF: JavadocType 9 | public class ServletExceptionTest { 10 | 11 | @Test 12 | public void shouldOfferStringConstructor() { 13 | Exception exception = new ServletException("SomeMessage"); 14 | assertThat(exception.getMessage(), is("SomeMessage")); 15 | } 16 | 17 | @Test 18 | public void shouldOfferStringThrowableConstructor() { 19 | Throwable e = new Exception(); 20 | Exception exception = new ServletException("SomeMessage", e); 21 | assertThat(exception.getMessage(), is("SomeMessage")); 22 | assertThat(exception.getCause(), is(e)); 23 | } 24 | 25 | @Test 26 | public void shouldThrowableConstructor() { 27 | Throwable e = new Exception(); 28 | Exception exception = new ServletException(e); 29 | assertThat(exception.getCause(), is(e)); 30 | } 31 | } 32 | // CHECKSTYLE.ON: JavadocType 33 | -------------------------------------------------------------------------------- /app/src/main/java/ro/polak/webserver/MainService.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.webserver; 9 | 10 | import android.content.Context; 11 | import androidx.annotation.NonNull; 12 | 13 | import impl.AndroidServerConfigFactory; 14 | import ro.polak.webserver.base.BaseMainService; 15 | import ro.polak.webserver.base.impl.BaseAndroidServerConfigFactory; 16 | 17 | /** 18 | * Main application service that holds http server. 19 | * 20 | * @author Piotr Polak piotr [at] polak [dot] ro 21 | * @since 201709 22 | */ 23 | public final class MainService extends BaseMainService { 24 | 25 | @NonNull 26 | @Override 27 | protected Class getActivityClass() { 28 | return MainActivity.class; 29 | } 30 | 31 | @NonNull 32 | @Override 33 | protected BaseAndroidServerConfigFactory getServerConfigFactory(final Context context) { 34 | return new AndroidServerConfigFactory(context); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /base/src/main/java/ro/polak/webserver/base/impl/AndroidCliServerGui.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.webserver.base.impl; 9 | 10 | import ro.polak.http.cli.DefaultCliServerGui; 11 | import ro.polak.http.configuration.ServerConfigFactory; 12 | 13 | /** 14 | * Server CLI interface along with a runner, used to test some of the Android only features. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201611 18 | */ 19 | public class AndroidCliServerGui extends DefaultCliServerGui { 20 | 21 | /** 22 | * The main CLI runner method. 23 | * 24 | * @param args 25 | */ 26 | public static void main(final String[] args) { 27 | (new AndroidCliServerGui()).init(); 28 | } 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | @Override 34 | protected ServerConfigFactory getServerConfigFactory() { 35 | return new BaseAndroidServerConfigFactory(null); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ServletPrintWriter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | package ro.polak.http.servlet; 8 | 9 | import java.io.OutputStream; 10 | import java.io.PrintWriter; 11 | 12 | /** 13 | * Adds additional writeEnd capability to the default PrintWriter. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 2001709 17 | */ 18 | public class ServletPrintWriter extends PrintWriter { 19 | 20 | private boolean wasWriteEndAlreadyCalled = false; 21 | 22 | public ServletPrintWriter(final OutputStream outputStream) { 23 | super(outputStream); 24 | } 25 | 26 | /** 27 | * Writes the end of the message. This method should be called once only. 28 | */ 29 | public void writeEnd() { 30 | if (wasWriteEndAlreadyCalled) { 31 | throw new IllegalStateException("This method can be called once only."); 32 | } 33 | wasWriteEndAlreadyCalled = true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/Filter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | 14 | /** 15 | * Filter interface. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201803 19 | */ 20 | public interface Filter { 21 | 22 | /** 23 | * Initializes filter. 24 | * 25 | * @param filterConfig 26 | * @throws ServletException 27 | */ 28 | void init(FilterConfig filterConfig) throws ServletException; 29 | 30 | /** 31 | * Performs request/response filtering. 32 | * 33 | * @param request 34 | * @param response 35 | * @param filterChain 36 | * @throws IOException 37 | * @throws ServletException 38 | */ 39 | void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 40 | throws IOException, ServletException; 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/exception/UnexpectedSituationExceptionTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.core.Is.is; 7 | 8 | // CHECKSTYLE.OFF: JavadocType 9 | public class UnexpectedSituationExceptionTest { 10 | 11 | @Test 12 | public void shouldOfferStringConstructor() { 13 | Exception exception = new UnexpectedSituationException("SomeMessage"); 14 | assertThat(exception.getMessage(), is("SomeMessage")); 15 | } 16 | 17 | @Test 18 | public void shouldOfferStringThrowableConstructor() { 19 | Throwable e = new Exception(); 20 | Exception exception = new UnexpectedSituationException("SomeMessage", e); 21 | assertThat(exception.getMessage(), is("SomeMessage")); 22 | assertThat(exception.getCause(), is(e)); 23 | } 24 | 25 | @Test 26 | public void shouldThrowableConstructor() { 27 | Throwable e = new Exception(); 28 | Exception exception = new UnexpectedSituationException(e); 29 | assertThat(exception.getCause(), is(e)); 30 | } 31 | } 32 | // CHECKSTYLE.ON: JavadocType 33 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/Loadable.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package ro.polak.http; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.servlet.impl.HttpServletRequestImpl; 13 | import ro.polak.http.servlet.impl.HttpServletResponseImpl; 14 | 15 | /** 16 | * Interface allowing to load payload into the response. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201912 20 | */ 21 | public interface Loadable { 22 | 23 | /** 24 | * Loads the resource for the given path by copying the stream to the response.getOutputStream(). 25 | * 26 | * @param path 27 | * @param request 28 | * @param response 29 | * @return 30 | * @throws IOException 31 | */ 32 | void load(String path, HttpServletRequestImpl request, HttpServletResponseImpl response) throws IOException; 33 | 34 | /** 35 | * Shuts down the resource provider if necessary, usually closes all open resources. 36 | */ 37 | void shutdown(); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/api/logic/MessageDTOMapper.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | package api.logic; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | import admin.logic.SmsBox; 15 | 16 | /** 17 | * Maps messages to JSONObjects. 18 | */ 19 | public class MessageDTOMapper { 20 | 21 | /** 22 | * Maps message to a JSON object. 23 | * 24 | * @param message 25 | * @return 26 | * @throws JSONException 27 | */ 28 | @NonNull 29 | public JSONObject toMessageDTO(final SmsBox.Message message) throws JSONException { 30 | JSONObject messageDTO = new JSONObject(); 31 | messageDTO.put("id", message.getId()); 32 | messageDTO.put("address", message.getAddress()); 33 | messageDTO.put("body", message.getBody()); 34 | messageDTO.put("date", message.getDate()); 35 | messageDTO.put("date_sent", message.getDateSent()); 36 | messageDTO.put("is_incoming", message.isIncoming()); 37 | return messageDTO; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/on_pr.yaml: -------------------------------------------------------------------------------- 1 | name: Build PR 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up JDK 11 23 | uses: actions/setup-java@v2 24 | with: 25 | java-version: '11' 26 | distribution: 'adopt' 27 | cache: gradle 28 | 29 | - name: Validate Gradle wrapper 30 | uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew clean build assemble :cli:fatJar --stacktrace 34 | 35 | - name: Cleanup Gradle Cache 36 | # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. 37 | # Restoring these files from a GitHub Actions cache might cause problems for future builds. 38 | run: | 39 | rm -f ~/.gradle/caches/modules-2/modules-2.lock 40 | rm -f ~/.gradle/caches/modules-2/gc.properties 41 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/PathHelper.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | /** 4 | * Contains methods used for path validation and manipulation. 5 | * 6 | * @author Piotr Polak piotr [at] polak [dot] ro 7 | * @since 201711 8 | */ 9 | public class PathHelper { 10 | 11 | private static final String SLASH = "/"; 12 | 13 | /** 14 | * Tells whether the given path contains illegal expressions. 15 | * 16 | * @param path 17 | * @return 18 | */ 19 | public boolean isPathContainingIllegalCharacters(final String path) { 20 | return path == null || path.startsWith("../") || path.indexOf("/../") != -1; 21 | } 22 | 23 | /** 24 | * Makes sure the last character is a slash. 25 | * 26 | * @param path 27 | * @return 28 | */ 29 | public String getNormalizedDirectoryPath(final String path) { 30 | if (isDirectoryPath(path)) { 31 | return path; 32 | } 33 | return path + SLASH; 34 | } 35 | 36 | /** 37 | * Tells whether the path ends with a slash. 38 | * 39 | * @param originalPath 40 | * @return 41 | */ 42 | public boolean isDirectoryPath(final String originalPath) { 43 | return originalPath.substring(originalPath.length() - 1).equals(SLASH); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /http/src/main/java/example/ChunkedServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.io.PrintWriter; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | import ro.polak.http.servlet.HttpServlet; 14 | import ro.polak.http.servlet.HttpServletRequest; 15 | import ro.polak.http.servlet.HttpServletResponse; 16 | 17 | import static ro.polak.http.Headers.HEADER_TRANSFER_ENCODING; 18 | 19 | /** 20 | * Chunked transfer example. 21 | */ 22 | public class ChunkedServlet extends HttpServlet { 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 29 | response.getHeaders().setHeader(HEADER_TRANSFER_ENCODING, "chunked"); 30 | PrintWriter printWriter = response.getWriter(); 31 | printWriter.print("This is an example of chunked transfer type. "); 32 | printWriter.flush(); 33 | printWriter.print("Chunked transfer type can be used when the final length of the data is not known."); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /http/src/main/java/example/StreamingServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.io.IOException; 11 | import java.nio.charset.StandardCharsets; 12 | 13 | import ro.polak.http.exception.ServletException; 14 | import ro.polak.http.servlet.HttpServlet; 15 | import ro.polak.http.servlet.HttpServletRequest; 16 | import ro.polak.http.servlet.HttpServletResponse; 17 | 18 | /** 19 | * Writing to output stream. 20 | */ 21 | public class StreamingServlet extends HttpServlet { 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | @Override 27 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 28 | try { 29 | byte[] message = "

Writing to output stream directly, without chunking.

".getBytes(StandardCharsets.UTF_8); 30 | response.setContentLength(message.length); 31 | response.getOutputStream().write(message); 32 | } catch (IOException e) { 33 | throw new ServletException("Unable to write to output stream", e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/impl/FilterChainImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet.impl; 9 | 10 | import java.io.IOException; 11 | import java.util.Deque; 12 | 13 | import ro.polak.http.exception.ServletException; 14 | import ro.polak.http.servlet.Filter; 15 | import ro.polak.http.servlet.FilterChain; 16 | import ro.polak.http.servlet.HttpServletRequest; 17 | import ro.polak.http.servlet.HttpServletResponse; 18 | 19 | /** 20 | * Default FilterChain implementation. 21 | * 22 | * @author Piotr Polak piotr [at] polak [dot] ro 23 | * @since 201803 24 | */ 25 | public class FilterChainImpl implements FilterChain { 26 | 27 | private Deque filters; 28 | 29 | public FilterChainImpl(final Deque filters) { 30 | this.filters = filters; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | public void doFilter(final HttpServletRequest request, final HttpServletResponse response) 38 | throws IOException, ServletException { 39 | 40 | filters.pop().doFilter(request, response, this); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/utilities/DateUtilities.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package ro.polak.http.utilities; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | import java.util.Locale; 13 | 14 | import static java.util.TimeZone.getTimeZone; 15 | 16 | /** 17 | * Thread safe date utilities. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 200804 21 | */ 22 | public final class DateUtilities { 23 | 24 | private static final String DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss z"; 25 | 26 | private DateUtilities() { 27 | } 28 | 29 | /** 30 | * Formats date into the RFC 822 GMT format. Thread safe. 31 | * 32 | * @param date 33 | * @return 34 | */ 35 | public static String dateFormat(final Date date) { 36 | return getNewDateFormat().format(date); 37 | } 38 | 39 | private static SimpleDateFormat getNewDateFormat() { 40 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US); 41 | simpleDateFormat.setTimeZone(getTimeZone("GMT")); 42 | 43 | return simpleDateFormat; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/PathHelperTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.core.Is.is; 7 | 8 | // CHECKSTYLE.OFF: JavadocType 9 | public class PathHelperTest { 10 | 11 | private static PathHelper pathHelper = new PathHelper(); 12 | 13 | @Test 14 | public void shouldNotAcceptInvalidCharacters() { 15 | assertThat(pathHelper.isPathContainingIllegalCharacters("somepath../"), is(false)); 16 | assertThat(pathHelper.isPathContainingIllegalCharacters("../somepath"), is(true)); 17 | assertThat(pathHelper.isPathContainingIllegalCharacters("somepath/../"), is(true)); 18 | assertThat(pathHelper.isPathContainingIllegalCharacters(null), is(true)); 19 | } 20 | 21 | @Test 22 | public void shouldNormalizeDirectoryPath() { 23 | assertThat(pathHelper.getNormalizedDirectoryPath("somepath"), is("somepath/")); 24 | assertThat(pathHelper.getNormalizedDirectoryPath("somepath/"), is("somepath/")); 25 | } 26 | 27 | @Test 28 | public void shouldSayWhetherDirectoryPath() { 29 | assertThat(pathHelper.isDirectoryPath("somepath"), is(false)); 30 | assertThat(pathHelper.isDirectoryPath("somepath/"), is(true)); 31 | } 32 | } 33 | // CHECKSTYLE.ON: JavadocType 34 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/session/storage/SessionStorage.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.session.storage; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.servlet.impl.HttpSessionImpl; 13 | 14 | /** 15 | * Specifies methods required for storing session. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201610 19 | */ 20 | public interface SessionStorage { 21 | 22 | /** 23 | * Persists session, throws exception in case of failure. 24 | * 25 | * @param session 26 | * @throws IOException 27 | */ 28 | void persistSession(HttpSessionImpl session) throws IOException; 29 | 30 | /** 31 | * Reads session for the given id. Returns null if there is no such session and throws 32 | * exception in case of failure. 33 | * 34 | * @param id 35 | * @return 36 | * @throws IOException 37 | */ 38 | HttpSessionImpl getSession(String id) throws IOException; 39 | 40 | /** 41 | * Removes session. 42 | * 43 | * @param session 44 | * @return 45 | */ 46 | boolean removeSession(HttpSessionImpl session); 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/impl/ServletMappingImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration.impl; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.configuration.ServletMapping; 13 | import ro.polak.http.servlet.HttpServlet; 14 | 15 | /** 16 | * Default implementation for ServletMapping. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201803 20 | */ 21 | public class ServletMappingImpl implements ServletMapping { 22 | 23 | private final Pattern urlPattern; 24 | 25 | private final Class servletClass; 26 | 27 | public ServletMappingImpl(final Pattern urlPattern, final Class servletClass) { 28 | this.urlPattern = urlPattern; 29 | this.servletClass = servletClass; 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public Pattern getUrlPattern() { 37 | return urlPattern; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public Class getServletClass() { 45 | return servletClass; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/utilities/StringUtilitiesTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.utilities; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.CoreMatchers.not; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static ro.polak.http.ExtraMarchers.utilityClass; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class StringUtilitiesTest { 12 | 13 | @Test 14 | public void shouldNotBeInstantiableFinalClass() { 15 | assertThat(StringUtilities.class, is(utilityClass())); 16 | } 17 | 18 | @Test 19 | public void shouldGenerateTwoDifferentRandomStrings() { 20 | String s1 = StringUtilities.generateRandom(); 21 | String s2 = StringUtilities.generateRandom(); 22 | 23 | // CHECKSTYLE.OFF: MagicNumber 24 | assertThat(s1.length(), is(32)); 25 | assertThat(s2.length(), is(32)); 26 | // CHECKSTYLE.ON: MagicNumber 27 | assertThat(s1, is(not(s2))); 28 | } 29 | 30 | @Test 31 | public void shouldDetectEmptyStrings() { 32 | assertThat(StringUtilities.isEmpty(null), is(true)); 33 | assertThat(StringUtilities.isEmpty(""), is(true)); 34 | 35 | assertThat(StringUtilities.isEmpty(" "), is(false)); 36 | assertThat(StringUtilities.isEmpty("a"), is(false)); 37 | } 38 | } 39 | // CHECKSTYLE.ON: JavadocType 40 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/RangePartHeader.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | package ro.polak.http; 8 | 9 | import ro.polak.http.servlet.Range; 10 | 11 | /** 12 | * Represents range part header. 13 | * 14 | * @author Piotr Polak piotr [at] polak [dot] ro 15 | * @since 201702 16 | */ 17 | public final class RangePartHeader { 18 | 19 | private final Range range; 20 | private final String boundary; 21 | private final String contentType; 22 | private final long totalLength; 23 | 24 | public RangePartHeader(final Range range, 25 | final String boundary, 26 | final String contentType, 27 | final long totalLength) { 28 | this.range = range; 29 | this.boundary = boundary; 30 | this.contentType = contentType; 31 | this.totalLength = totalLength; 32 | } 33 | 34 | public Range getRange() { 35 | return range; 36 | } 37 | 38 | public String getBoundary() { 39 | return boundary; 40 | } 41 | 42 | public String getContentType() { 43 | return contentType; 44 | } 45 | 46 | public long getTotalLength() { 47 | return totalLength; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/serializer/impl/HeadersSerializer.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.serializer.impl; 9 | 10 | import java.util.Set; 11 | 12 | import ro.polak.http.Headers; 13 | import ro.polak.http.protocol.serializer.Serializer; 14 | 15 | /** 16 | * Serializes headers to text representation. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201611 20 | */ 21 | public class HeadersSerializer implements Serializer { 22 | 23 | private static final String NEW_LINE = "\r\n"; 24 | private static final String KEY_VALUE_SEPARATOR = ": "; 25 | 26 | /** 27 | * Generates string representation of headers. 28 | * 29 | * @return 30 | */ 31 | @Override 32 | public String serialize(final Headers headers) { 33 | Set names = headers.keySet(); 34 | StringBuilder sb = new StringBuilder(); 35 | for (String name : names) { 36 | sb.append(name) 37 | .append(KEY_VALUE_SEPARATOR) 38 | .append(headers.getHeader(name)) 39 | .append(NEW_LINE); 40 | } 41 | sb.append(NEW_LINE); 42 | 43 | return sb.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /base/src/main/java/ro/polak/webserver/base/logic/AssetUtil.java: -------------------------------------------------------------------------------- 1 | package ro.polak.webserver.base.logic; 2 | 3 | import android.content.res.AssetManager; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | 11 | import ro.polak.http.utilities.IOUtilities; 12 | 13 | /** 14 | * Provides helper methods for managing/serving Android assets. 15 | * 16 | * @author Piotr Polak piotr [at] polak [dot] ro 17 | * @since 201709 18 | */ 19 | public final class AssetUtil { 20 | 21 | private AssetUtil() { 22 | 23 | } 24 | 25 | /** 26 | * Copies asset to file. 27 | * 28 | * @param assetManager 29 | * @param assetPath 30 | * @param destination 31 | * @throws IOException 32 | */ 33 | public static void copyAssetToFile(final AssetManager assetManager, 34 | final String assetPath, 35 | final File destination) 36 | throws IOException { 37 | 38 | InputStream in = null; 39 | OutputStream out = null; 40 | try { 41 | in = assetManager.open(assetPath); 42 | out = new FileOutputStream(destination); 43 | IOUtilities.copyStreams(in, out); 44 | } finally { 45 | IOUtilities.closeSilently(out); 46 | IOUtilities.closeSilently(in); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/admin/logic/FileIconMapper.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package admin.logic; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Maps extensions to file ICONS. 15 | */ 16 | public class FileIconMapper { 17 | 18 | private static final String ICON_EXT = ".png"; 19 | private static final Map ICONS = new HashMap<>(); 20 | 21 | static { 22 | ICONS.put("pdf", ""); 23 | ICONS.put("jpg", ""); 24 | ICONS.put("jpeg", "jpg"); 25 | ICONS.put("png", ""); 26 | ICONS.put("zip", ""); 27 | ICONS.put("gif", ""); 28 | } 29 | 30 | /** 31 | * Resolves icon path. If the extension is not known, a default path is returned. 32 | * 33 | * @param extension 34 | * @return 35 | */ 36 | public String getIconRelativePath(final String extension) { 37 | String ext = extension.toLowerCase(); 38 | 39 | if (ICONS.containsKey(ext)) { 40 | String iconPathKey = ICONS.get(ext); 41 | if (!"".equals(iconPathKey)) { 42 | return iconPathKey + ICON_EXT; 43 | } 44 | 45 | return ext + ICON_EXT; 46 | } 47 | 48 | return "default" + ICON_EXT; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/ServerRunnableTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.net.Socket; 7 | 8 | import ro.polak.http.configuration.ServerConfig; 9 | import ro.polak.http.errorhandler.HttpErrorHandlerResolver; 10 | import ro.polak.http.servlet.factory.HttpServletRequestImplFactory; 11 | import ro.polak.http.servlet.factory.HttpServletResponseImplFactory; 12 | 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.times; 15 | import static org.mockito.Mockito.verify; 16 | import static org.mockito.Mockito.when; 17 | 18 | // CHECKSTYLE.OFF: JavadocType 19 | public class ServerRunnableTest { 20 | 21 | @Test 22 | public void shouldLogIOExceptionsSilently() throws Exception { 23 | 24 | Socket socket = mock(Socket.class); 25 | HttpServletResponseImplFactory responseFactory = mock(HttpServletResponseImplFactory.class); 26 | 27 | when(responseFactory.createFromSocket(socket)).thenThrow(new IOException()); 28 | 29 | ServerRunnable serverRunnable = new ServerRunnable(socket, mock(ServerConfig.class), 30 | mock(HttpServletRequestImplFactory.class), 31 | responseFactory, 32 | mock(HttpErrorHandlerResolver.class), 33 | new PathHelper() 34 | ); 35 | 36 | serverRunnable.run(); 37 | 38 | verify(socket, times(1)).close(); 39 | } 40 | } 41 | // CHECKSTYLE.ON: JavadocType 42 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError405Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.Headers; 13 | import ro.polak.http.errorhandler.AbstractHtmlErrorHandler; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | 16 | /** 17 | * 405 Method Not Allowed HTTP error handler. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201509 21 | */ 22 | public class HttpError405Handler extends AbstractHtmlErrorHandler { 23 | 24 | private String allowedMethods; 25 | 26 | public HttpError405Handler(final String allowedMethods) { 27 | super(HttpServletResponse.STATUS_METHOD_NOT_ALLOWED, "Error 405 - Method Not Allowed", 28 | "

The method specified in the Request-Line is not allowed for the resource " 29 | + "identified by the Request-URI.

", null); 30 | this.allowedMethods = allowedMethods; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | public void serve(final HttpServletResponse response) throws IOException { 38 | response.getHeaders().setHeader(Headers.HEADER_ALLOW, allowedMethods); 39 | super.serve(response); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/Servlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import ro.polak.http.exception.ServletException; 11 | 12 | /** 13 | * HttpServlet v3 interface, declares service() method. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 200902 17 | */ 18 | public interface Servlet { 19 | 20 | /** 21 | * Initialization method that can be overwritten. 22 | * 23 | * @throws ServletException 24 | */ 25 | void init() throws ServletException; 26 | 27 | /** 28 | * The servlet initialization method. The reusable resources should be 29 | * initialized in the init method. 30 | * 31 | * @param servletConfig 32 | */ 33 | void init(ServletConfig servletConfig) throws ServletException; 34 | 35 | /** 36 | * Called by the container to indicate to a servlet that is is going to be taken out of service. 37 | */ 38 | void destroy(); 39 | 40 | /** 41 | * Called by servlet container, the main servlet logic method. 42 | */ 43 | void service(HttpServletRequest request, HttpServletResponse response) throws ServletException; /* IOException */ 44 | 45 | /** 46 | * Returns servlet info such as author or copyright. 47 | * 48 | * @return 49 | */ 50 | String getServletInfo(); 51 | } 52 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/protocol/serializer/impl/HeadersSerializerTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.protocol.serializer.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import ro.polak.http.Headers; 6 | import ro.polak.http.protocol.serializer.Serializer; 7 | 8 | import static org.hamcrest.CoreMatchers.anyOf; 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | 12 | // CHECKSTYLE.OFF: JavadocType 13 | public class HeadersSerializerTest { 14 | 15 | private static Serializer headersSerializer = new HeadersSerializer(); 16 | 17 | @Test 18 | public void shouldSerializeResponse() { 19 | Headers headers = new Headers(); 20 | headers.setHeader("Header", "Value"); 21 | headers.setHeader("SomeOtherHeader", "123"); 22 | 23 | assertThat(headersSerializer.serialize(headers), anyOf( 24 | is("SomeOtherHeader: 123\r\nHeader: Value\r\n\r\n"), 25 | is("Header: Value\r\nSomeOtherHeader: 123\r\n\r\n") 26 | )); 27 | } 28 | 29 | @Test 30 | public void shouldPreserveOriginalCase() { 31 | Headers headers = new Headers(); 32 | headers.setHeader("header", "Value"); 33 | headers.setHeader("someOtherHeader", "123"); 34 | 35 | assertThat(headersSerializer.serialize(headers), anyOf( 36 | is("someOtherHeader: 123\r\nheader: Value\r\n\r\n"), 37 | is("header: Value\r\nsomeOtherHeader: 123\r\n\r\n") 38 | )); 39 | } 40 | } 41 | // CHECKSTYLE.ON: JavadocType 42 | -------------------------------------------------------------------------------- /app/src/main/java/admin/filter/LogoutFilter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package admin.filter; 9 | 10 | import admin.logic.AccessControl; 11 | import ro.polak.http.configuration.ServerConfig; 12 | import ro.polak.http.servlet.Filter; 13 | import ro.polak.http.servlet.FilterChain; 14 | import ro.polak.http.servlet.FilterConfig; 15 | import ro.polak.http.servlet.HttpServletRequest; 16 | import ro.polak.http.servlet.HttpServletResponse; 17 | 18 | /** 19 | * Handles the logout logic. 20 | */ 21 | public class LogoutFilter implements Filter { 22 | 23 | private FilterConfig filterConfig; 24 | private ServerConfig serverConfig; 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | @Override 30 | public void init(final FilterConfig filterConfig) { 31 | this.filterConfig = filterConfig; 32 | serverConfig = (ServerConfig) filterConfig.getServletContext() 33 | .getAttribute(ServerConfig.class.getName()); 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | @Override 40 | public void doFilter(final HttpServletRequest request, final HttpServletResponse response, 41 | final FilterChain filterChain) { 42 | 43 | new AccessControl(serverConfig, request.getSession()).logout(); 44 | response.sendRedirect(filterConfig.getServletContext().getContextPath() + "/Login"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ServletContainer.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import ro.polak.http.exception.FilterInitializationException; 11 | import ro.polak.http.exception.ServletException; 12 | import ro.polak.http.exception.ServletInitializationException; 13 | 14 | /** 15 | * Manages life cycle of servlets. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 20170 19 | */ 20 | public interface ServletContainer { 21 | 22 | /** 23 | * Returns initialized servlet for given class name. 24 | * 25 | * @param servletClass 26 | * @param servletConfig 27 | * @return 28 | * @throws ServletInitializationException 29 | * @throws ServletException 30 | */ 31 | Servlet getServletForClass(Class servletClass, ServletConfig servletConfig) 32 | throws ServletInitializationException, ServletException; 33 | 34 | /** 35 | * Returns initialized servlet for given class name. 36 | * 37 | * @param filterClass 38 | * @return 39 | * @throws FilterInitializationException 40 | */ 41 | Filter getFilterForClass(Class filterClass, FilterConfig filterConfig) 42 | throws FilterInitializationException, ServletException; 43 | 44 | /** 45 | * Shuts down the servlet container, closes all open resources. 46 | */ 47 | void shutdown(); 48 | } 49 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/loader/SampleServlet.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet.loader; 2 | 3 | import ro.polak.http.exception.ServletException; 4 | import ro.polak.http.servlet.HttpServlet; 5 | import ro.polak.http.servlet.HttpServletRequest; 6 | import ro.polak.http.servlet.HttpServletResponse; 7 | import ro.polak.http.servlet.ServletConfig; 8 | 9 | // CHECKSTYLE.OFF: DesignForExtension JavadocType 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class SampleServlet extends HttpServlet { 12 | 13 | private int initializedCounter = 0; 14 | private int destroyedCounter = 0; 15 | 16 | private ServletConfig servletConfig; 17 | 18 | @Override 19 | public void init(final ServletConfig servletConfig) throws ServletException { 20 | this.servletConfig = servletConfig; 21 | init(); 22 | } 23 | 24 | @Override 25 | public void init() throws ServletException { 26 | super.init(); 27 | initializedCounter++; 28 | } 29 | 30 | @Override 31 | public void destroy() { 32 | super.destroy(); 33 | destroyedCounter++; 34 | } 35 | 36 | @Override 37 | public void service(final HttpServletRequest request, final HttpServletResponse response) { 38 | // Do nothing, this is used for test only 39 | } 40 | 41 | public int getInitializedCounter() { 42 | return initializedCounter; 43 | } 44 | 45 | public int getDestroyedCounter() { 46 | return destroyedCounter; 47 | } 48 | 49 | public ServletConfig getServletConfig() { 50 | return servletConfig; 51 | } 52 | } 53 | // CHECKSTYLE.ON: JavadocType 54 | // CHECKSTYLE.ON: DesignForExtension 55 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/utilities/FileUtilitiesTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.utilities; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.nullValue; 8 | import static ro.polak.http.ExtraMarchers.utilityClass; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class FileUtilitiesTest { 12 | 13 | @Test 14 | public void shouldNotBeInstantiableFinalClass() { 15 | assertThat(FileUtilities.class, is(utilityClass())); 16 | } 17 | 18 | @Test 19 | public void shouldReturnValidExtension() { 20 | assertThat(FileUtilities.getExtension("file.ext"), is("ext")); 21 | assertThat(FileUtilities.getExtension("/path/file.ext"), is("ext")); 22 | assertThat(FileUtilities.getExtension("file"), is("")); 23 | assertThat(FileUtilities.getExtension(null), is(nullValue())); 24 | } 25 | 26 | // CHECKSTYLE.OFF: MagicNumber 27 | @Test 28 | public void shouldFormatFileSize() { 29 | assertThat(FileUtilities.fileSizeUnits(1), is("1 B")); 30 | assertThat(FileUtilities.fileSizeUnits(1024), is("1.00 KB")); 31 | assertThat(FileUtilities.fileSizeUnits(1025), is("1.00 KB")); 32 | assertThat(FileUtilities.fileSizeUnits(1048576), is("1.00 MB")); 33 | assertThat(FileUtilities.fileSizeUnits(1048577), is("1.00 MB")); 34 | assertThat(FileUtilities.fileSizeUnits(1073741824), is("1.00 GB")); 35 | assertThat(FileUtilities.fileSizeUnits(1073741825), is("1.00 GB")); 36 | } 37 | // CHECKSTYLE.OFF: MagicNumber 38 | } 39 | // CHECKSTYLE.ON: JavadocType 40 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/HttpServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import ro.polak.http.exception.ServletException; 11 | 12 | /** 13 | * Default abstract servlet. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 200802 17 | */ 18 | public abstract class HttpServlet implements Servlet { 19 | 20 | private ServletConfig servletConfig; 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | @Override 26 | public void init(final ServletConfig servletConfig) throws ServletException { 27 | this.servletConfig = servletConfig; 28 | init(); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public void init() throws ServletException { 36 | // To be overwritten 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public void destroy() { 44 | // Empty by default, should be overwritten by the implementing servlet 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @Override 51 | public String getServletInfo() { 52 | return ""; 53 | } 54 | 55 | /** 56 | * Returns servlet context. 57 | * 58 | * @return 59 | */ 60 | public ServletContext getServletContext() { 61 | if (servletConfig == null) { 62 | return null; 63 | } 64 | return servletConfig.getServletContext(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/impl/HttpServletResponseImplIT.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.io.OutputStream; 9 | import java.net.Socket; 10 | 11 | import ro.polak.http.AbstractIT; 12 | import ro.polak.http.RequestBuilder; 13 | 14 | import static org.hamcrest.CoreMatchers.startsWith; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.junit.jupiter.api.Assertions.fail; 17 | 18 | // CHECKSTYLE.OFF: JavadocType 19 | public class HttpServletResponseImplIT extends AbstractIT { 20 | 21 | @Test 22 | public void shouldPrintHeadersFirstWhenWritingToOutputStream() throws IOException { 23 | String requestBody = RequestBuilder.defaultBuilder() 24 | .get("/example/Streaming") 25 | .withCloseConnection() 26 | .toString(); 27 | 28 | Socket socket = getSocket(); 29 | OutputStream out = socket.getOutputStream(); 30 | out.write(requestBody.getBytes()); 31 | BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 32 | 33 | String line; 34 | int numberOfLinesRead = 0; 35 | while ((line = in.readLine()) != null) { 36 | if (++numberOfLinesRead == 1) { 37 | assertThat(line, startsWith("HTTP/1.1 200")); 38 | } 39 | } 40 | 41 | if (numberOfLinesRead == 0) { 42 | fail("No server response was read"); 43 | } 44 | 45 | socket.close(); 46 | } 47 | } 48 | // CHECKSTYLE.ON: JavadocType 49 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/impl/FilterMappingImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration.impl; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.configuration.FilterMapping; 13 | import ro.polak.http.servlet.Filter; 14 | 15 | /** 16 | * Default implementation for FilterMapping. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201803 20 | */ 21 | public class FilterMappingImpl implements FilterMapping { 22 | 23 | private final Pattern urlPattern; 24 | private final Pattern urlExcludePattern; 25 | private final Class filterClass; 26 | 27 | public FilterMappingImpl(final Pattern urlPattern, 28 | final Pattern urlExcludePattern, 29 | final Class filterClass) { 30 | this.urlPattern = urlPattern; 31 | this.urlExcludePattern = urlExcludePattern; 32 | this.filterClass = filterClass; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | public Pattern getUrlPattern() { 40 | return urlPattern; 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | @Override 47 | public Pattern getUrlExcludePattern() { 48 | return urlExcludePattern; 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | @Override 55 | public Class getFilterClass() { 56 | return filterClass; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/ServletMappingBuilder.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.configuration.impl.ServletMappingImpl; 13 | import ro.polak.http.servlet.HttpServlet; 14 | 15 | /** 16 | * Utility for building servlet mapping configuration. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201803 20 | */ 21 | public final class ServletMappingBuilder { 22 | 23 | private final ServletContextBuilder servletContextBuilder; 24 | private Pattern urlPattern; 25 | private Class servletClass; 26 | 27 | /** 28 | * Created a mapping builder. This constructor should be package scoped. 29 | * 30 | * @param servletContextBuilder 31 | */ 32 | ServletMappingBuilder(final ServletContextBuilder servletContextBuilder) { 33 | this.servletContextBuilder = servletContextBuilder; 34 | } 35 | 36 | public ServletMappingBuilder withUrlPattern(final Pattern urlPattern) { 37 | this.urlPattern = urlPattern; 38 | return this; 39 | } 40 | 41 | public ServletMappingBuilder withServletClass(final Class servletClass) { 42 | this.servletClass = servletClass; 43 | return this; 44 | } 45 | 46 | public ServletContextBuilder end() { 47 | servletContextBuilder.withServletMapping(new ServletMappingImpl(urlPattern, servletClass)); 48 | return servletContextBuilder; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/AbstractPlainTextHttpErrorHandler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler; 9 | 10 | import java.io.IOException; 11 | 12 | import ro.polak.http.servlet.HttpServletResponse; 13 | import ro.polak.http.servlet.impl.HttpServletResponseImpl; 14 | 15 | /** 16 | * Abstract Http Error Handler. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201701 20 | */ 21 | public abstract class AbstractPlainTextHttpErrorHandler implements HttpErrorHandler { 22 | 23 | private final String status; 24 | private final String message; 25 | 26 | public AbstractPlainTextHttpErrorHandler(final String status, final String message) { 27 | this.status = status; 28 | this.message = message; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public void serve(final HttpServletResponse response) throws IOException { 36 | response.setStatus(status); 37 | response.setContentType("text/plain"); 38 | response.setContentLength(message.length()); 39 | response.getWriter().write(message); 40 | ((HttpServletResponseImpl) response).flush(); 41 | } 42 | 43 | /** 44 | * Returns status message. 45 | * @return 46 | */ 47 | protected String getStatus() { 48 | return status; 49 | } 50 | 51 | /** 52 | * Returns the message content. 53 | * @return 54 | */ 55 | protected String getMessage() { 56 | return message; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/HttpServletTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import ro.polak.http.exception.ServletException; 6 | import ro.polak.http.servlet.impl.ServletConfigImpl; 7 | 8 | import static org.hamcrest.CoreMatchers.equalTo; 9 | import static org.hamcrest.CoreMatchers.nullValue; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.core.Is.is; 12 | import static org.mockito.Mockito.mock; 13 | 14 | // CHECKSTYLE.OFF: JavadocType 15 | public class HttpServletTest { 16 | 17 | @Test 18 | public void shouldProvideDefaultValues() { 19 | HttpServlet httpServlet = new SampleServlet(); 20 | assertThat(httpServlet.getServletInfo(), is("")); 21 | assertThat(httpServlet.getServletContext(), is(nullValue())); 22 | httpServlet.destroy(); 23 | } 24 | 25 | @Test 26 | public void shouldProvideContextAfterInitialization() throws ServletException { 27 | HttpServlet httpServlet = new SampleServlet(); 28 | ServletContext servletContext = mock(ServletContext.class); 29 | ServletConfig servletConfig = new ServletConfigImpl(servletContext); 30 | httpServlet.init(servletConfig); 31 | assertThat(httpServlet.getServletInfo(), is("")); 32 | assertThat(httpServlet.getServletContext(), is(equalTo(servletContext))); 33 | httpServlet.destroy(); 34 | } 35 | 36 | private class SampleServlet extends HttpServlet { 37 | @Override 38 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 39 | // Do nothing, this is just a test servlet 40 | } 41 | } 42 | } 43 | // CHECKSTYLE.ON: JavadocType 44 | -------------------------------------------------------------------------------- /app/src/main/java/admin/IndexServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package admin; 9 | 10 | import admin.logic.HTMLDocument; 11 | import ro.polak.http.exception.ServletException; 12 | import ro.polak.http.servlet.HttpServlet; 13 | import ro.polak.http.servlet.HttpServletRequest; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | 16 | /** 17 | * Admin panel front page. 18 | */ 19 | public class IndexServlet extends HttpServlet { 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 26 | HTMLDocument doc = renderDocument(); 27 | response.getWriter().print(doc.toString()); 28 | } 29 | 30 | private HTMLDocument renderDocument() { 31 | HTMLDocument doc = new HTMLDocument("About"); 32 | doc.setOwnerClass(getClass().getSimpleName()); 33 | 34 | doc.writeln("

About

"); 35 | doc.write("

" + ro.polak.http.WebServer.SIGNATURE + " running.

"); 36 | doc.write("

Small multithread web server written completely in Java SE. "); 37 | doc.write("Implements most of the HTTP 1.1 specification. Uses JLWS Servlets for handling dynamic pages. "); 38 | doc.write("Supports cookies, sessions, file uploads.

"); 39 | doc.write("

Written by Piotr Polak. Visit homepage.

"); 41 | return doc; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /http/src/main/java/example/ChunkedWithDelayServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.io.PrintWriter; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | import ro.polak.http.servlet.HttpServlet; 14 | import ro.polak.http.servlet.HttpServletRequest; 15 | import ro.polak.http.servlet.HttpServletResponse; 16 | 17 | import static ro.polak.http.Headers.HEADER_TRANSFER_ENCODING; 18 | 19 | /** 20 | * Chunked transfer with delay example. 21 | */ 22 | public class ChunkedWithDelayServlet extends HttpServlet { 23 | 24 | private static final int CHUNKS_COUNT = 100; 25 | private static final int SLEEP_LENGTH_IN_MS = 30; 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | @Override 31 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 32 | response.getHeaders().setHeader(HEADER_TRANSFER_ENCODING, "chunked"); 33 | PrintWriter printWriter = response.getWriter(); 34 | printWriter.println(""); 35 | printWriter.println(""); 36 | for (int i = 0; i < CHUNKS_COUNT; i++) { 37 | try { 38 | Thread.sleep(SLEEP_LENGTH_IN_MS); 39 | } catch (InterruptedException e) { 40 | } 41 | printWriter.println(""); 42 | printWriter.flush(); 43 | } 44 | printWriter.println("
"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ChunkedPrintWriter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.io.OutputStream; 11 | 12 | /** 13 | * Allows flushing buffer in chunks. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @see Chunked transfer encoding 17 | * @since 201010 18 | */ 19 | public class ChunkedPrintWriter extends ServletPrintWriter { 20 | 21 | // TODO Move this capability to the ServletOutputStreamImpl 22 | 23 | private static final String NEW_LINE = "\r\n"; 24 | private static final String END_LINE = "0\r\n\r\n"; 25 | 26 | /** 27 | * Default constructor. 28 | * 29 | * @param out 30 | */ 31 | public ChunkedPrintWriter(final OutputStream out) { 32 | super(out); 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | public void println() { 40 | // Overwrites the original new line character 41 | synchronized (lock) { 42 | print(NEW_LINE); 43 | } 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | @Override 50 | public void write(final String str) { 51 | String head = Long.toHexString(str.length()).toUpperCase() + NEW_LINE; 52 | super.write(head); 53 | super.write(str); 54 | super.write(NEW_LINE); 55 | } 56 | 57 | /** 58 | * Writes the end of chunked message. 59 | */ 60 | @Override 61 | public void writeEnd() { 62 | super.writeEnd(); 63 | super.write(END_LINE); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/api/logic/APIResponse.java: -------------------------------------------------------------------------------- 1 | package api.logic; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | /** 8 | * Represents JSON API Response. 9 | */ 10 | public class APIResponse { 11 | 12 | public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json"; 13 | public static final int CODE_OK = 200; 14 | public static final int CODE_ERROR = 400; 15 | private static final String ATTR_STATUS = "status"; 16 | private static final String ATTR_MESSAGE = "message"; 17 | private static final String ATTR_RESULT = "result"; 18 | private static final String MESSAGE_OK = "OK"; 19 | 20 | private JSONObject jsonObject = new JSONObject(); 21 | 22 | /** 23 | * Serializes API Response. 24 | * 25 | * @param code 26 | * @param message 27 | * @param result 28 | * @throws JSONException 29 | */ 30 | public APIResponse(final int code, final String message, final JSONArray result) throws JSONException { 31 | jsonObject.put(ATTR_STATUS, code); 32 | jsonObject.put(ATTR_MESSAGE, message); 33 | jsonObject.put(ATTR_RESULT, result); 34 | } 35 | 36 | /** 37 | * Serializes API Response. 38 | * 39 | * @throws JSONException 40 | */ 41 | public APIResponse() throws JSONException { 42 | this(CODE_OK, MESSAGE_OK, null); 43 | } 44 | 45 | /** 46 | * Serializes API Response. 47 | * 48 | * @param code 49 | * @param message 50 | * @throws JSONException 51 | */ 52 | public APIResponse(final int code, final String message) throws JSONException { 53 | this(code, message, null); 54 | } 55 | 56 | /** 57 | * Returns object marshalled to JSON. 58 | * 59 | * @return 60 | */ 61 | @Override 62 | public String toString() { 63 | return jsonObject.toString(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/MultipartHeadersPart.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http; 9 | 10 | /** 11 | * Multipart request headers (for each multipart). 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @link http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html 15 | * @since 200802 16 | */ 17 | public class MultipartHeadersPart { 18 | 19 | private String fileName; 20 | private String contentType; 21 | private String name; 22 | 23 | /** 24 | * Returns the uploaded file name. 25 | * 26 | * @return 27 | */ 28 | public String getFileName() { 29 | return fileName; 30 | } 31 | 32 | /** 33 | * Returns the content type of the uploaded file. 34 | * 35 | * @return 36 | */ 37 | public String getContentType() { 38 | return contentType; 39 | } 40 | 41 | /** 42 | * Returns the name of the form post field. 43 | * 44 | * @return 45 | */ 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | /** 51 | * Sets the uploaded file name. 52 | * 53 | * @param fileName 54 | */ 55 | public void setFileName(final String fileName) { 56 | this.fileName = fileName; 57 | } 58 | 59 | /** 60 | * Sets the content type of the uploaded file. 61 | * 62 | * @param contentType 63 | */ 64 | public void setContentType(final String contentType) { 65 | this.contentType = contentType; 66 | } 67 | 68 | /** 69 | * Sets the name of the form post field. 70 | * 71 | * @param name 72 | */ 73 | public void setName(final String name) { 74 | this.name = name; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/impl/QueryStringParser.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.parser.impl; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import ro.polak.http.protocol.parser.MalformedInputException; 14 | import ro.polak.http.protocol.parser.Parser; 15 | import ro.polak.http.utilities.StringUtilities; 16 | 17 | /** 18 | * HTTP request headers wrapper. 19 | * 20 | * @author Piotr Polak piotr [at] polak [dot] ro 21 | * @since 201609 22 | */ 23 | public class QueryStringParser implements Parser> { 24 | 25 | private static final String PAIR_SEPARATOR = "&"; 26 | private static final String VALUE_SEPARATOR = "="; 27 | 28 | /** 29 | * Returns parsed query parameters. 30 | * 31 | * @param queryString 32 | * @return 33 | */ 34 | @Override 35 | public Map parse(final String queryString) throws MalformedInputException { 36 | Map parameters = new HashMap<>(); 37 | String[] queryParametersArray = queryString.split(PAIR_SEPARATOR); 38 | for (int i = 0; i < queryParametersArray.length; i++) { 39 | if (queryParametersArray[i].length() == 0) { 40 | continue; 41 | } 42 | 43 | String[] parameterPair = queryParametersArray[i].split(VALUE_SEPARATOR, 2); 44 | if (parameterPair[0].length() == 0) { 45 | continue; 46 | } 47 | 48 | if (parameterPair.length > 1) { 49 | parameters.put(parameterPair[0], StringUtilities.urlDecode(parameterPair[1])); 50 | } 51 | } 52 | 53 | return parameters; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/helper/RangeHelper.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | package ro.polak.http.servlet.helper; 8 | 9 | import java.util.List; 10 | 11 | import ro.polak.http.servlet.Range; 12 | 13 | /** 14 | * Range utilities. 15 | */ 16 | public class RangeHelper { 17 | 18 | /** 19 | * Returns computed range length. 20 | * 21 | * @param range 22 | * @return 23 | */ 24 | public long getRangeLength(final Range range) { 25 | return range.getTo() - range.getFrom() + 1; 26 | } 27 | 28 | /** 29 | * Tells whether the range is valid. 30 | * 31 | * @param range 32 | * @return 33 | */ 34 | public boolean isRangeValid(final Range range) { 35 | return (range.getFrom() > -1 && range.getTo() >= range.getFrom()); 36 | } 37 | 38 | /** 39 | * Computes total length of the provided ranges. 40 | * 41 | * @param ranges 42 | * @return 43 | */ 44 | public long getTotalLength(final List ranges) { 45 | int totalLength = 0; 46 | for (Range range : ranges) { 47 | totalLength += getRangeLength(range); 48 | } 49 | return totalLength; 50 | } 51 | 52 | /** 53 | * Tells whether the ranges are satisfiable for the given stream length. 54 | * 55 | * @param ranges 56 | * @param streamLength 57 | * @return 58 | */ 59 | public boolean isSatisfiable(final Iterable ranges, final long streamLength) { 60 | for (Range range : ranges) { 61 | if (range.getTo() >= streamLength || !isRangeValid(range)) { 62 | return false; 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /http/src/main/java/example/CookiesServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2015 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.util.Date; 11 | 12 | import ro.polak.http.exception.ServletException; 13 | import ro.polak.http.servlet.Cookie; 14 | import ro.polak.http.servlet.HttpServlet; 15 | import ro.polak.http.servlet.HttpServletRequest; 16 | import ro.polak.http.servlet.HttpServletResponse; 17 | 18 | /** 19 | * Cookie usage example page. 20 | */ 21 | public class CookiesServlet extends HttpServlet { 22 | 23 | private static final String PAGE_HITS_COOKIE_NAME = "page_hits"; 24 | private static final String FIRST_VISITED_AT_COOKIE_NAME = "first_visited_at"; 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | @Override 30 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 31 | int pageHits = 0; 32 | if (request.getCookie(PAGE_HITS_COOKIE_NAME) != null) { 33 | pageHits = Integer.parseInt(request.getCookie(PAGE_HITS_COOKIE_NAME).getValue()); 34 | } 35 | ++pageHits; 36 | response.addCookie(new Cookie(PAGE_HITS_COOKIE_NAME, pageHits)); 37 | 38 | String firstVisitedAt; 39 | if (request.getCookie(FIRST_VISITED_AT_COOKIE_NAME) != null) { 40 | firstVisitedAt = request.getCookie(FIRST_VISITED_AT_COOKIE_NAME).getValue(); 41 | } else { 42 | firstVisitedAt = (new Date()).toString(); 43 | response.addCookie(new Cookie(FIRST_VISITED_AT_COOKIE_NAME, firstVisitedAt)); 44 | } 45 | 46 | response.getWriter().println("

Cookie page hits: " + pageHits + "

"); 47 | response.getWriter().println("

First visited at: " + firstVisitedAt + "

"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/utilities/IOUtilities.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.utilities; 9 | 10 | import java.io.Closeable; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | 15 | /** 16 | * IOUtilities. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201702 20 | */ 21 | public final class IOUtilities { 22 | 23 | private static final int DEFAULT_BUFFER_SIZE = 4096; 24 | 25 | private IOUtilities() { 26 | } 27 | 28 | /** 29 | * Closes stream silently. 30 | * 31 | * @param closeable 32 | */ 33 | public static void closeSilently(final Closeable closeable) { 34 | if (closeable == null) { 35 | return; 36 | } 37 | try { 38 | closeable.close(); 39 | } catch (IOException ignored) { 40 | // Keep it silent 41 | } 42 | } 43 | 44 | /** 45 | * Copies input stream to output stream. 46 | * 47 | * @param in 48 | * @param out 49 | * @throws IOException 50 | */ 51 | public static void copyStreams(final InputStream in, final OutputStream out) throws IOException { 52 | copyStreams(in, out, DEFAULT_BUFFER_SIZE); 53 | } 54 | 55 | /** 56 | * Copies input stream to output stream. 57 | * 58 | * @param in 59 | * @param out 60 | * @throws IOException 61 | */ 62 | public static void copyStreams(final InputStream in, final OutputStream out, final int bufferSize) throws IOException { 63 | byte[] buffer = new byte[bufferSize]; 64 | int length; 65 | while ((length = in.read(buffer)) > 0) { 66 | out.write(buffer, 0, length); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/UploadedFile.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.io.File; 11 | 12 | /** 13 | * Uploaded file representation. 14 | * 15 | * @author Piotr Polak piotr [at] polak [dot] ro 16 | * @since 200802 17 | */ 18 | public final class UploadedFile { 19 | 20 | private final String postFieldName; 21 | private final String fileName; 22 | private final File file; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param postFieldName 28 | * @param fileName 29 | * @param file 30 | */ 31 | public UploadedFile(final String postFieldName, final String fileName, final File file) { 32 | this.postFieldName = postFieldName; 33 | this.fileName = fileName; 34 | this.file = file; 35 | } 36 | 37 | /** 38 | * Deletes temporary file if the file has not been moved to another location. 39 | * 40 | * @return true if deleted 41 | */ 42 | public boolean destroy() { 43 | if (file.exists()) { 44 | return file.delete(); 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * Returns the HTML form postFieldName. 52 | * 53 | * @return the HTML form postFieldName 54 | */ 55 | public String getPostFieldName() { 56 | return postFieldName; 57 | } 58 | 59 | /** 60 | * Returns the postFieldName of uploaded file. 61 | * 62 | * @return the postFieldName of uploaded file 63 | */ 64 | public String getFileName() { 65 | return fileName; 66 | } 67 | 68 | /** 69 | * Returns uploaded file. 70 | * 71 | * @return 72 | */ 73 | public File getFile() { 74 | return file; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/impl/CookieParser.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.parser.impl; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import ro.polak.http.protocol.parser.MalformedInputException; 14 | import ro.polak.http.protocol.parser.Parser; 15 | import ro.polak.http.servlet.Cookie; 16 | import ro.polak.http.utilities.StringUtilities; 17 | 18 | /** 19 | * Cookie parser utility. 20 | * 21 | * @author Piotr Polak piotr [at] polak [dot] ro 22 | * @since 201611 23 | */ 24 | public class CookieParser implements Parser> { 25 | 26 | private static final String VALUE_SEPARATOR = "="; 27 | private static final String COOKIE_SEPARATOR = ";"; 28 | 29 | /** 30 | * Parses cookie string, returns an array representing cookies read. 31 | * 32 | * @param input 33 | * @return 34 | * @throws MalformedInputException 35 | */ 36 | @Override 37 | public Map parse(final String input) throws MalformedInputException { 38 | Map cookies = new HashMap<>(); 39 | 40 | // Splitting separate cookies array 41 | String[] cookiesStr = input.split(COOKIE_SEPARATOR); 42 | for (int i = 0; i < cookiesStr.length; i++) { 43 | // Splitting cookie name=value pair 44 | String[] cookieValues = cookiesStr[i].split(VALUE_SEPARATOR, 2); 45 | String cookieName = cookieValues[0].trim(); 46 | if (cookieValues.length > 1 && cookieName.length() > 0) { 47 | Cookie cookie = new Cookie(cookieName, StringUtilities.urlDecode(cookieValues[1])); 48 | cookies.put(cookie.getName(), cookie); 49 | } 50 | } 51 | 52 | return cookies; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/FileUtils.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | 9 | import ro.polak.http.impl.ServerConfigImplTest; 10 | import ro.polak.http.utilities.IOUtilities; 11 | 12 | // CHECKSTYLE.OFF: JavadocType 13 | public final class FileUtils { 14 | 15 | private FileUtils() { 16 | } 17 | 18 | /** 19 | * Creates a temporary directory and returns its path. 20 | * The directory will be deleted on JVM close. 21 | * 22 | * @return 23 | * @throws IOException 24 | */ 25 | public static String createTempDirectory() throws IOException { 26 | File file = Files.createTempDirectory(ServerConfigImplTest.class.getName()).toFile(); 27 | file.deleteOnExit(); 28 | return file.getAbsolutePath() + "/"; 29 | } 30 | 31 | /** 32 | * Writes contents to a temporary file and returns the file. 33 | * 34 | * @param contents 35 | * @return 36 | * @throws IOException 37 | */ 38 | public static File writeToTempFile(final String contents) throws IOException { 39 | File file = File.createTempFile("temp", ".util"); 40 | file.deleteOnExit(); 41 | writeToFile(file, contents); 42 | return file; 43 | } 44 | 45 | /** 46 | * Writes string contents to the given file. 47 | * 48 | * @param file 49 | * @param contents 50 | * @throws IOException 51 | */ 52 | public static void writeToFile(final File file, final String contents) throws IOException { 53 | BufferedWriter writer = null; 54 | try { 55 | writer = new BufferedWriter(new FileWriter(file)); 56 | writer.write(contents); 57 | } finally { 58 | if (writer != null) { 59 | IOUtilities.closeSilently(writer); 60 | } 61 | } 62 | } 63 | } 64 | // CHECKSTYLE.ON: JavadocType 65 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/ServletContext.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet; 9 | 10 | import java.util.Enumeration; 11 | import java.util.List; 12 | 13 | import ro.polak.http.configuration.FilterMapping; 14 | import ro.polak.http.configuration.ServletMapping; 15 | 16 | /** 17 | * Servlet context. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201610 21 | */ 22 | public interface ServletContext { 23 | 24 | /** 25 | * Sets context attribute. 26 | * 27 | * @param name 28 | * @param value 29 | * @throws IllegalStateException 30 | */ 31 | void setAttribute(String name, Object value); 32 | 33 | /** 34 | * Gets context attribute of the specified name. 35 | * 36 | * @param name Attribute name 37 | * @return 38 | * @throws IllegalStateException 39 | */ 40 | Object getAttribute(String name); 41 | 42 | /** 43 | * Returns enumeration representing attribute names. 44 | * 45 | * @return 46 | * @throws IllegalStateException 47 | */ 48 | Enumeration getAttributeNames(); 49 | 50 | /** 51 | * Returns the MIME type of the specified file, or null if the MIME type is not known. 52 | * 53 | * @param file 54 | * @return 55 | */ 56 | String getMimeType(String file); 57 | 58 | /** 59 | * Returns servlet URL pattern to servlet class mappings. 60 | * 61 | * @return 62 | */ 63 | List getServletMappings(); 64 | 65 | /** 66 | * Returns filter URL pattern to filter class mappings. 67 | * 68 | * @return 69 | */ 70 | List getFilterMappings(); 71 | 72 | /** 73 | * Returns servlet context path. 74 | * 75 | * @return 76 | */ 77 | String getContextPath(); 78 | } 79 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/ServiceUnavailableHandler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http; 9 | 10 | import java.io.IOException; 11 | import java.net.Socket; 12 | import java.util.concurrent.RejectedExecutionHandler; 13 | import java.util.concurrent.ThreadPoolExecutor; 14 | 15 | import ro.polak.http.errorhandler.impl.HttpError503Handler; 16 | import ro.polak.http.servlet.factory.HttpServletResponseImplFactory; 17 | import ro.polak.http.utilities.IOUtilities; 18 | 19 | /** 20 | * ServiceUnavailableHandler is responsible for sending 503 error pages when there is more space 21 | * in the runnable queue. To test this class you have to limit the number of available threads 22 | * and queue size to 1 and then to try open multiple connections at the same time. 23 | * 24 | * @author Piotr Polak piotr [at] polak [dot] ro 25 | * @since 201610 26 | */ 27 | public class ServiceUnavailableHandler implements RejectedExecutionHandler { 28 | 29 | private final HttpServletResponseImplFactory responseFactory; 30 | 31 | /** 32 | * Default constructor. 33 | * 34 | * @param responseFactory 35 | */ 36 | public ServiceUnavailableHandler(final HttpServletResponseImplFactory responseFactory) { 37 | this.responseFactory = responseFactory; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { 45 | if (r instanceof ServerRunnable) { 46 | Socket socket = ((ServerRunnable) r).getSocket(); 47 | try { 48 | (new HttpError503Handler()).serve(responseFactory.createFromSocket(socket)); 49 | } catch (IOException e) { 50 | } finally { 51 | IOUtilities.closeSilently(socket); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/utilities/IOUtilitiesTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.utilities; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.Closeable; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.core.Is.is; 14 | import static org.junit.jupiter.api.Assertions.fail; 15 | import static org.mockito.Mockito.doThrow; 16 | import static org.mockito.Mockito.mock; 17 | import static ro.polak.http.ExtraMarchers.utilityClass; 18 | 19 | // CHECKSTYLE.OFF: JavadocType 20 | public class IOUtilitiesTest { 21 | 22 | @Test 23 | public void shouldNotBeInstantiableFinalClass() { 24 | assertThat(IOUtilities.class, is(utilityClass())); 25 | } 26 | 27 | @Test 28 | public void shouldCloseClosableSilently() throws IOException { 29 | Closeable closeable = mock(Closeable.class); 30 | doThrow(new IOException("This should never happen")).when(closeable).close(); 31 | 32 | try { 33 | closeable.close(); 34 | fail("IOException should be thrown."); 35 | } catch (Exception e) { 36 | } 37 | 38 | IOUtilities.closeSilently(closeable); 39 | } 40 | 41 | @Test 42 | public void shouldCloseNullClosableSilently() { 43 | try { 44 | IOUtilities.closeSilently(null); 45 | } catch (Exception e) { 46 | fail("Exception should not be thrown."); 47 | } 48 | } 49 | 50 | @Test 51 | public void shouldCopyStreams() throws IOException { 52 | String input = "image/jpeg jPEG jPG jPE"; 53 | InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); 54 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 55 | IOUtilities.copyStreams(in, out); 56 | assertThat(out.toString(), is(input)); 57 | } 58 | } 59 | // CHECKSTYLE.ON: JavadocType 60 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/ChunkedPrintWriterTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | public class ChunkedPrintWriterTest { 12 | 13 | private static final int ONE_KILO = 1024; 14 | 15 | @Test 16 | public void shouldSerializeDataProperly() { 17 | ByteArrayOutputStream out = new ByteArrayOutputStream(ONE_KILO); 18 | ChunkedPrintWriter printWriter = new ChunkedPrintWriter(out); 19 | 20 | printWriter.print("Wiki"); 21 | printWriter.print("pedia"); 22 | printWriter.print(" in\r\n\r\nchunks."); 23 | printWriter.writeEnd(); 24 | printWriter.flush(); 25 | assertThat(new String(out.toByteArray()), is("4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n")); 26 | } 27 | 28 | @Test 29 | public void shouldAppendNewLineProperly() { 30 | ByteArrayOutputStream out = new ByteArrayOutputStream(ONE_KILO); 31 | ChunkedPrintWriter printWriter = new ChunkedPrintWriter(out); 32 | 33 | printWriter.print("Wiki"); 34 | printWriter.println(); 35 | printWriter.flush(); 36 | assertThat(new String(out.toByteArray()), is("4\r\nWiki\r\n2\r\n\r\n\r\n")); 37 | } 38 | 39 | @Test 40 | public void shouldEncodeLengthAsHex() { 41 | ByteArrayOutputStream out = new ByteArrayOutputStream(ONE_KILO); 42 | ChunkedPrintWriter printWriter = new ChunkedPrintWriter(out); 43 | 44 | printWriter.print("SomeTextLongerThanSixteenCharacters"); 45 | printWriter.flush(); 46 | assertThat(new String(out.toByteArray()), is("23\r\nSomeTextLongerThanSixteenCharacters\r\n")); 47 | 48 | printWriter.writeEnd(); 49 | printWriter.flush(); 50 | assertThat(new String(out.toByteArray()), is("23\r\nSomeTextLongerThanSixteenCharacters\r\n0\r\n\r\n")); 51 | } 52 | } 53 | // CHECKSTYLE.ON: JavadocType 54 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/servlet/factory/HttpServletResponseImplFactory.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.servlet.factory; 9 | 10 | import java.io.IOException; 11 | import java.net.Socket; 12 | 13 | import ro.polak.http.Headers; 14 | import ro.polak.http.protocol.serializer.Serializer; 15 | import ro.polak.http.servlet.Cookie; 16 | import ro.polak.http.servlet.helper.StreamHelper; 17 | import ro.polak.http.servlet.impl.HttpServletResponseImpl; 18 | 19 | /** 20 | * Utility facilitating creating new responses out of the socket. 21 | * 22 | * @author Piotr Polak piotr [at] polak [dot] ro 23 | * @since 201710 24 | */ 25 | public class HttpServletResponseImplFactory { 26 | 27 | private final Serializer headersSerializer; 28 | private final Serializer cookieHeaderSerializer; 29 | private final StreamHelper streamHelper; 30 | 31 | /** 32 | * Default constructor. 33 | * 34 | * @param headersSerializer 35 | * @param cookieHeaderSerializer 36 | * @param streamHelper 37 | */ 38 | public HttpServletResponseImplFactory(final Serializer headersSerializer, 39 | final Serializer cookieHeaderSerializer, 40 | final StreamHelper streamHelper) { 41 | this.headersSerializer = headersSerializer; 42 | this.cookieHeaderSerializer = cookieHeaderSerializer; 43 | this.streamHelper = streamHelper; 44 | } 45 | 46 | /** 47 | * Creates and returns a response outputStream of the socket. 48 | * 49 | * @param socket 50 | * @return 51 | */ 52 | public HttpServletResponseImpl createFromSocket(final Socket socket) throws IOException { 53 | return new HttpServletResponseImpl(headersSerializer, 54 | cookieHeaderSerializer, 55 | streamHelper, 56 | socket.getOutputStream()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /http/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'jacoco' 3 | apply plugin: 'info.solidsoft.pitest' 4 | 5 | compileJava { 6 | sourceCompatibility = 1.7 7 | } 8 | 9 | compileTestJava { 10 | sourceCompatibility = 1.8 11 | } 12 | 13 | // Added here to enable http subproject to be built without the root project 14 | repositories { 15 | jcenter() 16 | } 17 | 18 | buildscript { 19 | repositories { 20 | jcenter() 21 | } 22 | dependencies { 23 | classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.6.0' 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0' 30 | testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.0' 31 | testImplementation 'org.mockito:mockito-core:3.12.4' 32 | testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4' 33 | testImplementation 'org.hamcrest:hamcrest-library:2.2' 34 | testImplementation 'com.squareup.okhttp3:okhttp:4.9.1' 35 | testImplementation project(path: ':cli') 36 | } 37 | 38 | jacocoTestReport { 39 | reports { 40 | xml.enabled = true 41 | html.enabled = true 42 | } 43 | afterEvaluate { 44 | getClassDirectories().setFrom(files(classDirectories.files.collect { 45 | fileTree(dir: it, 46 | exclude: ['example/*.class']) 47 | })) 48 | } 49 | } 50 | 51 | // More details at https://github.com/szpak/gradle-pitest-plugin/blob/master/src/main/groovy/info/solidsoft/gradle/pitest/PitestPluginExtension.groovy 52 | pitest { 53 | targetClasses = ['ro.polak.http.*'] 54 | avoidCallsTo = ['ro.polak.http.Statistics', 55 | 'ro.polak.http.utilities.IOUtilities', 56 | 'java.util.logging.Logger'] 57 | threads = 4 58 | outputFormats = ['XML', 'HTML'] 59 | timestampedReports = false 60 | junit5PluginVersion = '0.12' 61 | } 62 | 63 | check.dependsOn jacocoTestReport 64 | 65 | checkstyle { 66 | sourceSets = [project.sourceSets.main, project.sourceSets.test] 67 | } 68 | 69 | test { 70 | useJUnitPlatform() 71 | } 72 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/RequestStatus.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http; 9 | 10 | /** 11 | * HTTP status representation. 12 | * 13 | * @author Piotr Polak piotr [at] polak [dot] ro 14 | * @since 201611 15 | */ 16 | public class RequestStatus { 17 | 18 | private String method; 19 | private String uri; 20 | private String queryString; 21 | private String protocol; 22 | 23 | /** 24 | * Returns HTTP method. 25 | * 26 | * @return 27 | */ 28 | public String getMethod() { 29 | return method; 30 | } 31 | 32 | /** 33 | * Sets HTTP method. 34 | * 35 | * @param method 36 | */ 37 | public void setMethod(final String method) { 38 | this.method = method; 39 | } 40 | 41 | /** 42 | * Returns requested URI. The URI does not contain query string. 43 | * 44 | * @return 45 | */ 46 | public String getUri() { 47 | return uri; 48 | } 49 | 50 | /** 51 | * Sets requested URI. The URI must not contain query string. 52 | * 53 | * @param uri 54 | */ 55 | public void setUri(final String uri) { 56 | this.uri = uri; 57 | } 58 | 59 | /** 60 | * Returns request query string. 61 | * 62 | * @return 63 | */ 64 | public String getQueryString() { 65 | return queryString; 66 | } 67 | 68 | /** 69 | * Sets request query string. 70 | * 71 | * @param queryString 72 | */ 73 | public void setQueryString(final String queryString) { 74 | this.queryString = queryString; 75 | } 76 | 77 | /** 78 | * Returns HTTP protocol. 79 | * 80 | * @return 81 | */ 82 | public String getProtocol() { 83 | return protocol; 84 | } 85 | 86 | /** 87 | * Sets HTTP protocol. 88 | * 89 | * @param protocol 90 | */ 91 | public void setProtocol(final String protocol) { 92 | this.protocol = protocol; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /http/src/main/java/example/SessionServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import ro.polak.http.exception.ServletException; 11 | import ro.polak.http.servlet.HttpServlet; 12 | import ro.polak.http.servlet.HttpServletRequest; 13 | import ro.polak.http.servlet.HttpServletResponse; 14 | 15 | /** 16 | * Session usage example page. 17 | */ 18 | public class SessionServlet extends HttpServlet { 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 25 | 26 | // Saving session attribute name in a variable for convenience 27 | String attributeName = "pageHits"; 28 | 29 | // Resetting the counter 30 | int pageHits = 0; 31 | 32 | // Getting the page hits from session if exists 33 | if (request.getSession().getAttribute(attributeName) != null) { 34 | // Please note the session attribute is of String type 35 | pageHits = Integer.parseInt((String) request.getSession().getAttribute(attributeName)); 36 | } 37 | 38 | // Incrementing hits counter 39 | ++pageHits; 40 | 41 | // Persisting incremented value in session 42 | request.getSession().setAttribute(attributeName, Integer.toString(pageHits)); 43 | 44 | // Printing out the result 45 | response.getWriter().println("

Session page hits: " + pageHits + "

"); 46 | response.getWriter().println("

Session is new: " + request.getSession().isNew() + "

"); 47 | response.getWriter().println("

Session creation time: " + request.getSession().getCreationTime() + "

"); 48 | response.getWriter().println("

Session last accessed time: " 49 | + request.getSession().getLastAccessedTime() + "

"); 50 | response.getWriter().println("

Session max inactive interval in seconds: " 51 | + request.getSession().getMaxInactiveInterval() + "

"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/RequestBuilder.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import ro.polak.http.protocol.serializer.Serializer; 4 | import ro.polak.http.protocol.serializer.impl.HeadersSerializer; 5 | 6 | /** 7 | * Makes it easier to build requests for tests. 8 | */ 9 | public final class RequestBuilder { 10 | 11 | private static final String NEW_LINE = "\r\n"; 12 | 13 | private String method; 14 | private String uri; 15 | private String protocol = "HTTP/1.1"; 16 | private Headers headers = new Headers(); 17 | private Serializer headersSerializer = new HeadersSerializer(); 18 | 19 | public static RequestBuilder defaultBuilder() { 20 | RequestBuilder rb = new RequestBuilder(); 21 | return rb; 22 | } 23 | 24 | public RequestBuilder get(final String uri) { 25 | return method("GET", uri); 26 | } 27 | 28 | public RequestBuilder withProtocol(final String protocol) { 29 | this.protocol = protocol; 30 | return this; 31 | } 32 | 33 | public RequestBuilder method(final String method, final String uri) { 34 | this.method = method; 35 | this.uri = uri; 36 | return this; 37 | } 38 | 39 | public RequestBuilder withHeader(final String name, final String value) { 40 | headers.setHeader(name, value); 41 | return this; 42 | } 43 | 44 | public RequestBuilder withCloseConnection() { 45 | this.withHeader("Connection", "close"); 46 | return this; 47 | } 48 | 49 | public RequestBuilder withHost(final String value) { 50 | this.withHeader("Host", value); 51 | return this; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | StringBuilder sb = new StringBuilder(); 57 | 58 | if (method != null) { 59 | sb.append(method) 60 | .append(" "); 61 | } 62 | if (uri != null) { 63 | sb.append(uri) 64 | .append(" "); 65 | } 66 | if (protocol != null) { 67 | sb.append(protocol); 68 | } 69 | sb.append(NEW_LINE) 70 | .append(headersSerializer.serialize(headers)); 71 | 72 | 73 | return sb.toString(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/FilterMappingBuilder.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import ro.polak.http.configuration.impl.FilterMappingImpl; 13 | import ro.polak.http.servlet.Filter; 14 | 15 | /** 16 | * Utility for building filter mapping configuration. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 201803 20 | */ 21 | public class FilterMappingBuilder { 22 | 23 | private final ServletContextBuilder servletContextBuilder; 24 | private Pattern urlPattern; 25 | private Pattern urlExcludedPattern; 26 | private Class clazz; 27 | 28 | /** 29 | * Created a mapping builder. This constructor should be package scoped. 30 | * 31 | * @param servletContextBuilder 32 | */ 33 | FilterMappingBuilder(final ServletContextBuilder servletContextBuilder) { 34 | this.servletContextBuilder = servletContextBuilder; 35 | } 36 | 37 | /** 38 | * @param urlPattern 39 | * @return 40 | */ 41 | public FilterMappingBuilder withUrlPattern(final Pattern urlPattern) { 42 | this.urlPattern = urlPattern; 43 | return this; 44 | } 45 | 46 | /** 47 | * @param urlExcludedPattern 48 | * @return 49 | */ 50 | public FilterMappingBuilder withUrlExcludedPattern(final Pattern urlExcludedPattern) { 51 | this.urlExcludedPattern = urlExcludedPattern; 52 | return this; 53 | } 54 | 55 | /** 56 | * @param clazz 57 | * @return 58 | */ 59 | public FilterMappingBuilder withFilterClass(final Class clazz) { 60 | this.clazz = clazz; 61 | return this; 62 | } 63 | 64 | /** 65 | * Returns the constructed object. 66 | * 67 | * @return 68 | */ 69 | public ServletContextBuilder end() { 70 | servletContextBuilder.withFilterMapping(new FilterMappingImpl(urlPattern, urlExcludedPattern, clazz)); 71 | return servletContextBuilder; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/protocol/parser/impl/RequestStatusParserTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.protocol.parser.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.function.Executable; 5 | 6 | import ro.polak.http.RequestStatus; 7 | import ro.polak.http.protocol.parser.MalformedInputException; 8 | import ro.polak.http.protocol.parser.Parser; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.junit.jupiter.api.Assertions.assertThrows; 13 | 14 | // CHECKSTYLE.OFF: JavadocType 15 | public class RequestStatusParserTest { 16 | 17 | @Test 18 | public void shouldParseStatusString() throws MalformedInputException { 19 | Parser requestStatusParser = new RequestStatusParser(); 20 | RequestStatus requestStatus = requestStatusParser.parse("GET /home?param1=ABC¶m2=123 HTTP/1.1"); 21 | 22 | assertThat(requestStatus.getMethod(), is("GET")); 23 | assertThat(requestStatus.getQueryString(), is("param1=ABC¶m2=123")); 24 | assertThat(requestStatus.getUri(), is("/home")); 25 | assertThat(requestStatus.getProtocol(), is("HTTP/1.1")); 26 | } 27 | 28 | @Test 29 | public void shouldIgnoreTrailingCharacters() throws MalformedInputException { 30 | Parser requestStatusParser = new RequestStatusParser(); 31 | RequestStatus requestStatus = requestStatusParser.parse("GET /home?param1=ABC¶m2=123 HTTP/1.1\r\n"); 32 | 33 | assertThat(requestStatus.getMethod(), is("GET")); 34 | assertThat(requestStatus.getQueryString(), is("param1=ABC¶m2=123")); 35 | assertThat(requestStatus.getUri(), is("/home")); 36 | assertThat(requestStatus.getProtocol(), is("HTTP/1.1")); 37 | } 38 | 39 | @Test 40 | public void shouldThrowMalformedInputExceptionOnInvalidStatus() { 41 | final Parser requestStatusParser = new RequestStatusParser(); 42 | assertThrows(MalformedInputException.class, new Executable() { 43 | @Override 44 | public void execute() throws MalformedInputException { 45 | requestStatusParser.parse("GET HTTP/1.1"); 46 | } 47 | }); 48 | } 49 | } 50 | // CHECKSTYLE.ON: JavadocType 51 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/DeploymentDescriptorBuilder.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2018-2018 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import ro.polak.http.servlet.impl.ServletContextImpl; 14 | import ro.polak.http.session.storage.SessionStorage; 15 | 16 | /** 17 | * Utility for building servlet context configuration. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201803 21 | */ 22 | public final class DeploymentDescriptorBuilder { 23 | 24 | private final List servletContextImpls = new ArrayList<>(); 25 | 26 | private SessionStorage sessionStorage; 27 | private ServerConfig serverConfig; 28 | 29 | /** 30 | * This constructor is intentionally private. 31 | */ 32 | private DeploymentDescriptorBuilder() { 33 | } 34 | 35 | public static DeploymentDescriptorBuilder create() { 36 | return new DeploymentDescriptorBuilder(); 37 | } 38 | 39 | public DeploymentDescriptorBuilder withSessionStorage(final SessionStorage sessionStorage) { 40 | this.sessionStorage = sessionStorage; 41 | return this; 42 | } 43 | 44 | public DeploymentDescriptorBuilder withServerConfig(final ServerConfig serverConfig) { 45 | this.serverConfig = serverConfig; 46 | return this; 47 | } 48 | 49 | public ServletContextBuilder addServletContext() { 50 | return new ServletContextBuilder(this, sessionStorage, serverConfig); 51 | } 52 | 53 | public List build() { 54 | return servletContextImpls; 55 | } 56 | 57 | /** 58 | * Adds a servlet context. This method should be package scoped. 59 | * 60 | * @param servletContextImpl 61 | * @return 62 | */ 63 | protected DeploymentDescriptorBuilder addServletContext(final ServletContextImpl servletContextImpl) { 64 | servletContextImpl.setAttribute(ServerConfig.class.getName(), serverConfig); 65 | servletContextImpls.add(servletContextImpl); 66 | return this; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/HeadersTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.CoreMatchers.nullValue; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.contains; 10 | 11 | // CHECKSTYLE.OFF: JavadocType 12 | public final class HeadersTest { 13 | 14 | private Headers headers; 15 | 16 | @BeforeEach 17 | public void setUp() { 18 | headers = new Headers(); 19 | } 20 | 21 | @Test 22 | public void shouldSetAndGetHeaders() { 23 | headers.setHeader("Cookie", "ABCD"); 24 | assertThat(headers.getHeader("Cookie"), is("ABCD")); 25 | 26 | headers.setHeader("Cookie", "FFF"); 27 | assertThat(headers.getHeader("Cookie"), is("FFF")); 28 | 29 | assertThat("FFF", is(headers.getHeader("Cookie"))); 30 | } 31 | 32 | @Test 33 | public void shouldSetAndGetHeadersCaseInsensitive() { 34 | headers.setHeader("Cookie", "ABCD"); 35 | assertThat(headers.getHeader("COOKIE"), is("ABCD")); 36 | } 37 | 38 | @Test 39 | public void shouldSetAndContainHeadersCaseInsensitive() { 40 | headers.setHeader("Cookie", "ABCD"); 41 | assertThat(headers.containsHeader("COOKIE"), is(true)); 42 | } 43 | 44 | @Test 45 | public void shouldReturnNullValueForInexistentHeader() { 46 | assertThat(headers.getHeader("Non-existent"), is(nullValue())); 47 | } 48 | 49 | @Test 50 | public void shouldHaveHeaderNamesCaseInsensitive() { 51 | headers.setHeader("Cookie", "ABCD"); 52 | assertThat(headers.keySet().size(), is(1)); 53 | headers.setHeader("COOKIE", "1234"); 54 | assertThat(headers.keySet().size(), is(1)); 55 | assertThat(headers.keySet(), contains("Cookie")); 56 | } 57 | 58 | @Test 59 | public void shouldSetLatestValueToTheHeader() { 60 | headers.setHeader("Cookie", "ABCD"); 61 | assertThat(headers.keySet().size(), is(1)); 62 | headers.setHeader("COOKIE", "1234"); 63 | assertThat(headers.keySet().size(), is(1)); 64 | assertThat(headers.getHeader("Cookie"), is("1234")); 65 | } 66 | } 67 | // CHECKSTYLE.ON: JavadocType 68 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/impl/RequestStatusParser.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2016-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.protocol.parser.impl; 9 | 10 | import ro.polak.http.RequestStatus; 11 | import ro.polak.http.protocol.parser.MalformedInputException; 12 | import ro.polak.http.protocol.parser.Parser; 13 | 14 | /** 15 | * Utility for parsing HTTP status line. 16 | * 17 | * @author Piotr Polak piotr [at] polak [dot] ro 18 | * @since 201611 19 | */ 20 | public class RequestStatusParser implements Parser { 21 | 22 | private static final int NUMBER_OF_CHUNKS = 3; 23 | private static final String STATUS_SEPARATOR = " "; 24 | private static final String QUERY_STRING_START = "?"; 25 | 26 | /** 27 | * Parses status line. 28 | * 29 | * @param input 30 | * @return 31 | * @throws MalformedInputException 32 | */ 33 | @Override 34 | public RequestStatus parse(final String input) throws MalformedInputException { 35 | RequestStatus status = new RequestStatus(); 36 | status.setQueryString(""); 37 | String uri; 38 | 39 | 40 | String[] statusArray = input.split(STATUS_SEPARATOR, NUMBER_OF_CHUNKS); 41 | 42 | if (statusArray.length < NUMBER_OF_CHUNKS) { 43 | throw new MalformedInputException("Input status string should be composed out of " 44 | + NUMBER_OF_CHUNKS + " chunks. Received " + input); 45 | } 46 | 47 | // First element of the array is the HTTP method 48 | status.setMethod(statusArray[0].toUpperCase()); 49 | // Second element of the array is the HTTP queryString 50 | uri = statusArray[1]; 51 | 52 | // Protocol is the third part of the status line 53 | status.setProtocol(statusArray[2].trim()); 54 | 55 | int questionMarkPosition = uri.indexOf(QUERY_STRING_START); 56 | if (questionMarkPosition > -1) { 57 | status.setQueryString(uri.substring(questionMarkPosition + 1)); 58 | uri = uri.substring(0, questionMarkPosition); 59 | } 60 | status.setUri(uri); 61 | return status; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/admin/filter/SecurityFilter.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2018 6 | **************************************************/ 7 | 8 | package admin.filter; 9 | 10 | import static admin.LoginServlet.RELOCATE_PARAM_NAME; 11 | 12 | import java.io.IOException; 13 | 14 | import admin.logic.AccessControl; 15 | import ro.polak.http.configuration.ServerConfig; 16 | import ro.polak.http.exception.ServletException; 17 | import ro.polak.http.servlet.Filter; 18 | import ro.polak.http.servlet.FilterChain; 19 | import ro.polak.http.servlet.FilterConfig; 20 | import ro.polak.http.servlet.HttpServletRequest; 21 | import ro.polak.http.servlet.HttpServletResponse; 22 | 23 | /** 24 | * Provides a security check before executing the servlet logic. 25 | */ 26 | public class SecurityFilter implements Filter { 27 | 28 | private FilterConfig filterConfig; 29 | private ServerConfig serverConfig; 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public void init(final FilterConfig filterConfig) { 36 | this.filterConfig = filterConfig; 37 | serverConfig = (ServerConfig) filterConfig.getServletContext() 38 | .getAttribute(ServerConfig.class.getName()); 39 | } 40 | 41 | /** 42 | * {@inheritDoc} 43 | */ 44 | @Override 45 | public void doFilter(final HttpServletRequest request, final HttpServletResponse response, 46 | final FilterChain filterChain) throws IOException, ServletException { 47 | 48 | AccessControl accessControl = new AccessControl(serverConfig, request.getSession()); 49 | if (!accessControl.isLogged()) { 50 | String url = filterConfig.getServletContext().getContextPath() + getLoginUri(request); 51 | response.sendRedirect(url); 52 | return; 53 | } 54 | 55 | filterChain.doFilter(request, response); 56 | } 57 | 58 | private String getLoginUri(final HttpServletRequest request) { 59 | String uri = "/Login?" + RELOCATE_PARAM_NAME + "=" + request.getRequestURI(); 60 | 61 | 62 | if (!"".equals(request.getQueryString())) { 63 | uri += "?" + request.getQueryString(); 64 | } 65 | 66 | return uri; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/impl/ServletOutputStreamImpl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.impl; 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | 13 | import ro.polak.http.servlet.ServletOutputStream; 14 | import ro.polak.http.servlet.impl.HttpServletResponseImpl; 15 | 16 | /** 17 | * Adds possibility flush headers capability to the ordinary output stream. 18 | * 19 | * @since 201611 20 | */ 21 | public class ServletOutputStreamImpl extends ServletOutputStream { 22 | 23 | private final OutputStream outputStream; 24 | private final HttpServletResponseImpl response; 25 | 26 | /** 27 | * Default constructor. 28 | * 29 | * @param outputStream 30 | * @param httpResponse 31 | */ 32 | public ServletOutputStreamImpl(final OutputStream outputStream, final HttpServletResponseImpl httpResponse) { 33 | this.outputStream = outputStream; 34 | this.response = httpResponse; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | @Override 41 | public void write(final int b) throws IOException { 42 | flushHeadersIfPossible(); 43 | outputStream.write(b); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | @Override 50 | public void write(final byte[] b) throws IOException { 51 | flushHeadersIfPossible(); 52 | outputStream.write(b); 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | @Override 59 | public void write(final byte[] b, final int off, final int len) throws IOException { 60 | flushHeadersIfPossible(); 61 | outputStream.write(b, off, len); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | */ 67 | @Override 68 | public void flush() throws IOException { 69 | outputStream.flush(); 70 | } 71 | 72 | private void flushHeadersIfPossible() throws IOException { 73 | if (!response.isCommitted()) { 74 | response.flushHeaders(); 75 | } 76 | } 77 | 78 | /** 79 | * {@inheritDoc} 80 | */ 81 | @Override 82 | public void close() throws IOException { 83 | outputStream.close(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/impl/MimeTypeMappingImplTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | import ro.polak.http.MimeTypeMapping; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | 15 | // CHECKSTYLE.OFF: JavadocType 16 | public class MimeTypeMappingImplTest { 17 | 18 | @Test 19 | public void shouldSuportMultivaluedLine() throws IOException { 20 | String input = "image/jpeg jpeg jpg jpe"; 21 | InputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); 22 | MimeTypeMapping mimeTypeMapping = MimeTypeMappingImpl.createFromStream(stream); 23 | assertThat(mimeTypeMapping.getMimeTypeByExtension("jpg"), is("image/jpeg")); 24 | assertThat(mimeTypeMapping.getMimeTypeByExtension("jpeg"), is("image/jpeg")); 25 | assertThat(mimeTypeMapping.getMimeTypeByExtension("jpe"), is("image/jpeg")); 26 | stream.close(); 27 | } 28 | 29 | @Test 30 | public void shouldNormalizeLetterCase() throws IOException { 31 | String input = "image/jpeg jPEG jPG jPE"; 32 | InputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); 33 | MimeTypeMapping mimeTypeMapping = MimeTypeMappingImpl.createFromStream(stream); 34 | assertThat(mimeTypeMapping.getMimeTypeByExtension("Jpg"), is("image/jpeg")); 35 | assertThat(mimeTypeMapping.getMimeTypeByExtension("Jpeg"), is("image/jpeg")); 36 | assertThat(mimeTypeMapping.getMimeTypeByExtension("Jpe"), is("image/jpeg")); 37 | stream.close(); 38 | } 39 | 40 | @Test 41 | public void shouldReturnDefaultMimeType() throws IOException { 42 | String input = "image/jpeg jPEG jPG jPE"; 43 | InputStream stream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); 44 | MimeTypeMapping mimeTypeMapping = MimeTypeMappingImpl.createFromStream(stream, "default/default"); 45 | assertThat(mimeTypeMapping.getMimeTypeByExtension("any"), is("default/default")); 46 | assertThat(mimeTypeMapping.getMimeTypeByExtension(null), is("default/default")); 47 | stream.close(); 48 | } 49 | } 50 | // CHECKSTYLE.ON: JavadocType 51 | -------------------------------------------------------------------------------- /app/src/main/java/admin/ServerStatsServlet.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package admin; 9 | 10 | import admin.logic.HTMLDocument; 11 | import ro.polak.http.exception.ServletException; 12 | import ro.polak.http.servlet.HttpServlet; 13 | import ro.polak.http.servlet.HttpServletRequest; 14 | import ro.polak.http.servlet.HttpServletResponse; 15 | import ro.polak.http.utilities.FileUtilities; 16 | 17 | /** 18 | * Statistics. 19 | */ 20 | public class ServerStatsServlet extends HttpServlet { 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | @Override 26 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 27 | HTMLDocument doc = renderDocument(); 28 | response.getWriter().print(doc.toString()); 29 | } 30 | 31 | private HTMLDocument renderDocument() { 32 | HTMLDocument doc = new HTMLDocument("Statistics"); 33 | doc.setOwnerClass(getClass().getSimpleName()); 34 | 35 | doc.writeln("

Server statistics

"); 36 | doc.writeln("

Please refresh the page to update the statistics."); 37 | 38 | doc.writeln(""); 39 | doc.writeln(""); 40 | doc.writeln(" "); 42 | doc.writeln(""); 43 | doc.writeln(""); 44 | doc.writeln(" "); 46 | doc.writeln(""); 47 | doc.writeln(""); 48 | doc.writeln(" "); 49 | doc.writeln(""); 50 | doc.writeln(""); 51 | doc.writeln(" "); 52 | doc.writeln(""); 53 | doc.writeln(""); 54 | doc.writeln(" "); 55 | doc.writeln(""); 56 | doc.writeln(" 5 | * Copyright (c) Piotr Polak 2008-2023 6 | **************************************************/ 7 | 8 | package example; 9 | 10 | import java.io.FileInputStream; 11 | import java.io.IOException; 12 | import java.io.PrintWriter; 13 | 14 | import ro.polak.http.exception.ServletException; 15 | import ro.polak.http.servlet.HttpServlet; 16 | import ro.polak.http.servlet.HttpServletRequest; 17 | import ro.polak.http.servlet.HttpServletResponse; 18 | import ro.polak.http.servlet.UploadedFile; 19 | import ro.polak.http.utilities.IOUtilities; 20 | 21 | /** 22 | * File Upload example page. 23 | */ 24 | public class FileUploadServlet extends HttpServlet { 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | @Override 30 | public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { 31 | if ("get".equalsIgnoreCase(request.getMethod())) { 32 | PrintWriter printWriter = response.getWriter(); 33 | printWriter.println("

Uploaded file will be returned in the response

"); 34 | printWriter.println(""); 35 | printWriter.println(""); 36 | printWriter.println(""); 37 | printWriter.println(""); 38 | } else { 39 | if (request.getUploadedFiles().isEmpty()) { 40 | PrintWriter printWriter = response.getWriter(); 41 | printWriter.println("

No files uploaded

"); 42 | } else { 43 | UploadedFile uploadedFile = request.getUploadedFiles().iterator().next(); 44 | FileInputStream fileInputStream = null; 45 | response.setContentType("application/octet-stream"); 46 | try { 47 | fileInputStream = new FileInputStream(uploadedFile.getFile()); 48 | IOUtilities.copyStreams(fileInputStream, response.getOutputStream()); 49 | } catch (IOException e) { 50 | throw new ServletException(e); 51 | } finally { 52 | IOUtilities.closeSilently(fileInputStream); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/protocol/parser/impl/RangeParser.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2017-2017 6 | **************************************************/ 7 | package ro.polak.http.protocol.parser.impl; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import ro.polak.http.protocol.parser.MalformedInputException; 13 | import ro.polak.http.protocol.parser.Parser; 14 | import ro.polak.http.servlet.Range; 15 | 16 | /** 17 | * Parses range headers. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201702 21 | */ 22 | public class RangeParser implements Parser> { 23 | 24 | private static final String START_WORD = "bytes="; 25 | private static final String RANGES_SEPARATOR = ","; 26 | private static final String RANGE_SEPARATOR = "-"; 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | @Override 32 | public List parse(final String input) throws MalformedInputException { 33 | List rangeList = new ArrayList<>(); 34 | 35 | String inputNormalized = input.toLowerCase().trim(); 36 | if (!inputNormalized.startsWith(START_WORD)) { 37 | throw new MalformedInputException("Header value must start with bytes="); 38 | } 39 | 40 | String[] rangesString = inputNormalized.substring(START_WORD.length()).split(RANGES_SEPARATOR); 41 | for (String rangeString : rangesString) { 42 | if (rangeString.indexOf(RANGE_SEPARATOR) == -1) { 43 | throw new MalformedInputException("Invalid range value " + rangeString); 44 | } 45 | 46 | String[] values = rangeString.split(RANGE_SEPARATOR); 47 | 48 | if (values.length != 2) { 49 | throw new MalformedInputException("Invalid range value " + rangeString); 50 | } 51 | 52 | rangeList.add(getRange(values)); 53 | } 54 | 55 | return rangeList; 56 | } 57 | 58 | private Range getRange(final String[] values) throws MalformedInputException { 59 | try { 60 | return new Range(Long.parseLong(values[0].trim()), Long.parseLong(values[1].trim())); 61 | } catch (NumberFormatException e) { 62 | throw new MalformedInputException("Invalid range value, unable to parse numeric values " + e.getMessage()); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/errorhandler/impl/HttpError500Handler.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2017 6 | **************************************************/ 7 | 8 | package ro.polak.http.errorhandler.impl; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.PrintStream; 12 | import java.io.UnsupportedEncodingException; 13 | import java.nio.charset.StandardCharsets; 14 | 15 | import ro.polak.http.errorhandler.AbstractHtmlErrorHandler; 16 | import ro.polak.http.servlet.HttpServletResponse; 17 | import ro.polak.http.utilities.StringUtilities; 18 | 19 | /** 20 | * 500 Internal Server Error HTTP error handler. 21 | * 22 | * @author Piotr Polak piotr [at] polak [dot] ro 23 | * @since 201509 24 | */ 25 | public class HttpError500Handler extends AbstractHtmlErrorHandler { 26 | 27 | public HttpError500Handler() { 28 | super(HttpServletResponse.STATUS_INTERNAL_SERVER_ERROR, "Error 500 - The server made a boo boo", 29 | "

No further details are provided

", null); 30 | } 31 | 32 | /** 33 | * Sets the reason and generates error message for 500 HTTP error. 34 | * 35 | * @param e Throwable 36 | */ 37 | public HttpError500Handler setReason(final Throwable e) { 38 | 39 | StringBuilder stringBuilder = new StringBuilder(); 40 | stringBuilder.append("

"); 41 | if (!StringUtilities.isEmpty(e.getMessage())) { 42 | stringBuilder.append(e.getMessage() + " "); 43 | } 44 | stringBuilder.append(e.getClass().getName() + "

\n") 45 | .append("
")
46 |                 .append(exceptionToString(e))
47 |                 .append("
"); 48 | 49 | setExplanation(stringBuilder.toString()); 50 | 51 | return this; 52 | } 53 | 54 | private String exceptionToString(final Throwable e) { 55 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 56 | PrintStream printStream = new PrintStream(os); 57 | 58 | e.printStackTrace(printStream); 59 | try { 60 | return os.toString(StandardCharsets.UTF_8.name()); 61 | } catch (UnsupportedEncodingException ignored) { 62 | // We are using a well known charset. This is not supposed to happen. 63 | return ignored.getMessage(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/CookieTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.function.Executable; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.is; 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | 10 | // CHECKSTYLE.OFF: JavadocType 11 | // CHECKSTYLE.OFF: MagicNumber 12 | public class CookieTest { 13 | 14 | @Test 15 | public void shouldNotAllowIllegalName() { 16 | 17 | assertThrows(IllegalArgumentException.class, new Executable() { 18 | @Override 19 | public void execute() { 20 | new Cookie(";illegal", "somevalue"); 21 | } 22 | }); 23 | } 24 | 25 | @Test 26 | public void shouldWorkGettersAndSetters() { 27 | Cookie cookie = new Cookie("someName", "someValue"); 28 | cookie.setComment("comment"); 29 | cookie.setDomain("example.com"); 30 | cookie.setPath("/somepath"); 31 | cookie.setSecure(true); 32 | cookie.setHttpOnly(true); 33 | assertThat(cookie.getName(), is("someName")); 34 | assertThat(cookie.getValue(), is("someValue")); 35 | cookie.setValue("SomeValue2"); 36 | assertThat(cookie.getValue(), is("SomeValue2")); 37 | assertThat(cookie.getComment(), is("comment")); 38 | assertThat(cookie.getDomain(), is("example.com")); 39 | assertThat(cookie.getPath(), is("/somepath")); 40 | assertThat(cookie.isSecure(), is(true)); 41 | assertThat(cookie.isHttpOnly(), is(true)); 42 | assertThat(cookie.getMaxAge(), is(-1)); 43 | cookie.setMaxAge(125); 44 | assertThat(cookie.getMaxAge(), is(125)); 45 | } 46 | 47 | @Test 48 | public void shouldAllowBooleanValues() { 49 | Cookie cookie = new Cookie("someName", true); 50 | assertThat(cookie.getValue(), is("true")); 51 | } 52 | 53 | @Test 54 | public void shouldAllowIntValues() { 55 | Cookie cookie = new Cookie("someName", 14); 56 | assertThat(cookie.getValue(), is("14")); 57 | } 58 | 59 | @Test 60 | public void shouldAllowLongValues() { 61 | Cookie cookie = new Cookie("someName", 1545454454544844L); 62 | assertThat(cookie.getValue(), is("1545454454544844")); 63 | } 64 | 65 | @Test 66 | public void shouldAllowDoubleValues() { 67 | Cookie cookie = new Cookie("someName", 22.33); 68 | assertThat(cookie.getValue(), is("22.33")); 69 | } 70 | } 71 | // CHECKSTYLE.ON: MagicNumber 72 | // CHECKSTYLE.ON: JavadocType 73 | -------------------------------------------------------------------------------- /.github/workflows/on_master.yaml: -------------------------------------------------------------------------------- 1 | name: Build Master 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up JDK 11 23 | uses: actions/setup-java@v2 24 | with: 25 | java-version: '11' 26 | distribution: 'adopt' 27 | cache: gradle 28 | 29 | - name: Validate Gradle wrapper 30 | uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew clean build assemble :cli:fatJar --stacktrace 34 | 35 | - name: Upload Code Coverage report 36 | uses: codecov/codecov-action@v2 37 | with: 38 | token: ${{ secrets.CODECOV_TOKEN }} 39 | files: ./http/build/reports/jacoco/test/jacocoTestReport.xml 40 | flags: unittests 41 | 42 | - name: Prepare release 43 | run: | 44 | mkdir -p ./app/build/_release/android-http-server 45 | mv ./app/build/outputs/apk/release/app-release-unsigned.apk ./app/build/_release/android-http-server/android-http-server-latest-unsigned.apk 46 | mv ./cli/build/libs/cli-all.jar ./app/build/_release/android-http-server/android-http-server-cli-fatjar-latest.jar 47 | 48 | - name: Upload APK to Dropbox 49 | uses: deka0106/upload-to-dropbox@v2.0.0 50 | with: 51 | dropbox_access_token: ${{ secrets.DROPBOX_TOKEN }} 52 | src: ./app/build/_release/android-http-server/android-http-server-latest-unsigned.apk 53 | dest: /android-http-server/ 54 | mode: overwrite 55 | 56 | - name: Upload CLI jar to Dropbox 57 | uses: deka0106/upload-to-dropbox@v2.0.0 58 | with: 59 | dropbox_access_token: ${{ secrets.DROPBOX_TOKEN }} 60 | src: ./app/build/_release/android-http-server/android-http-server-cli-fatjar-latest.jar 61 | dest: /android-http-server/ 62 | mode: overwrite 63 | 64 | - name: Cleanup Gradle Cache 65 | # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. 66 | # Restoring these files from a GitHub Actions cache might cause problems for future builds. 67 | run: | 68 | rm -f ~/.gradle/caches/modules-2/modules-2.lock 69 | rm -f ~/.gradle/caches/modules-2/gc.properties 70 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/protocol/parser/impl/CookieParserTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.protocol.parser.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import ro.polak.http.protocol.parser.MalformedInputException; 8 | import ro.polak.http.protocol.parser.Parser; 9 | import ro.polak.http.servlet.Cookie; 10 | import ro.polak.http.utilities.StringUtilities; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.collection.IsMapContaining.hasKey; 15 | 16 | // CHECKSTYLE.OFF: JavadocType 17 | public class CookieParserTest { 18 | 19 | private static Parser> cookieParser = new CookieParser(); 20 | 21 | @Test 22 | public void shouldParseCookieHavingSpaceInValue() throws MalformedInputException { 23 | String value = "value containing spaces"; 24 | Map cookies = cookieParser.parse("name=" + value); 25 | assertThat(cookies, hasKey("name")); 26 | assertThat(cookies.get("name").getValue(), is(value)); 27 | } 28 | 29 | @Test 30 | public void shouldParseCookieHavingUrlEncodedValue() throws MalformedInputException { 31 | String value = "&<>some value"; 32 | Map cookies = cookieParser.parse("name=" + StringUtilities.urlEncode(value)); 33 | assertThat(cookies, hasKey("name")); 34 | assertThat(cookies.get("name").getValue(), is(value)); 35 | } 36 | 37 | @Test 38 | public void shouldTrimCookieNameValue() throws MalformedInputException { 39 | Map cookies = cookieParser.parse(" name ="); 40 | assertThat(cookies, hasKey("name")); 41 | } 42 | 43 | @Test 44 | public void shouldParseEmptyValue() throws MalformedInputException { 45 | Map cookies = cookieParser.parse(""); 46 | assertThat(cookies.size(), is(0)); 47 | } 48 | 49 | @Test 50 | public void shouldReturnZeroSizeForInvalidValue() throws MalformedInputException { 51 | Map cookies = cookieParser.parse("name"); 52 | assertThat(cookies.size(), is(0)); 53 | } 54 | 55 | @Test 56 | public void shouldReturnZeroSizeForInvalidKey() throws MalformedInputException { 57 | Map cookies = cookieParser.parse(" = value"); 58 | assertThat(cookies.size(), is(0)); 59 | } 60 | 61 | @Test 62 | public void shouldParseMalformedEmptyValue() throws MalformedInputException { 63 | Map cookies = cookieParser.parse(" ; "); 64 | assertThat(cookies.size(), is(0)); 65 | } 66 | } 67 | // CHECKSTYLE.ON: JavadocType 68 | -------------------------------------------------------------------------------- /app/src/main/java/admin/logic/AccessControl.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package admin.logic; 9 | 10 | import java.util.logging.Logger; 11 | 12 | import ro.polak.http.configuration.ServerConfig; 13 | import ro.polak.http.servlet.HttpSession; 14 | 15 | /** 16 | * Access control support class. 17 | * 18 | * @author Piotr Polak piotr [at] polak [dot] ro 19 | * @since 200802 20 | */ 21 | public class AccessControl { 22 | 23 | private static final Logger LOGGER = Logger.getLogger(AccessControl.class.getName()); 24 | public static final String ATTR_LOGGEDIN = "loggedin"; 25 | 26 | private ServerConfig serverConfig; 27 | private HttpSession session; 28 | 29 | 30 | /** 31 | * Default constructor. 32 | * 33 | * @param serverConfig 34 | * @param session 35 | */ 36 | public AccessControl(final ServerConfig serverConfig, final HttpSession session) { 37 | this.serverConfig = serverConfig; 38 | this.session = session; 39 | } 40 | 41 | /** 42 | * Tells whether the user is logged. 43 | * 44 | * @return 45 | */ 46 | public boolean isLogged() { 47 | if (session == null) { 48 | LOGGER.fine("No session, not logged in"); 49 | return false; 50 | } 51 | 52 | if (session.getAttribute(ATTR_LOGGEDIN) != null) { 53 | if (session.getAttribute(ATTR_LOGGEDIN).equals("1")) { 54 | return true; 55 | } else { 56 | LOGGER.fine("Not logging in - session attribute is NOT null"); 57 | } 58 | } else { 59 | LOGGER.fine("Not logging in - session attribute is null"); 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * Logs off the currently logged user. 67 | */ 68 | public void logout() { 69 | session.setAttribute(ATTR_LOGGEDIN, null); 70 | } 71 | 72 | /** 73 | * Logs the user in if the login and password match. 74 | * 75 | * @param login 76 | * @param password 77 | * @return 78 | */ 79 | public boolean doLogin(final String login, final String password) { 80 | if (serverConfig.getAttribute("admin.login").equals(login) 81 | && serverConfig.getAttribute("admin.password").equals(password)) { 82 | session.setAttribute(ATTR_LOGGEDIN, "1"); 83 | return true; 84 | } else { 85 | LOGGER.fine("Not logging in - wrong password"); 86 | } 87 | return false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/servlet/impl/HttpServletResponseImplTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.servlet.impl; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.function.Executable; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | 10 | import ro.polak.http.Headers; 11 | import ro.polak.http.protocol.serializer.Serializer; 12 | import ro.polak.http.servlet.helper.StreamHelper; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.is; 16 | import static org.junit.jupiter.api.Assertions.assertThrows; 17 | import static org.junit.jupiter.api.Assertions.fail; 18 | import static org.mockito.Mockito.mock; 19 | 20 | // CHECKSTYLE.OFF: JavadocType 21 | public final class HttpServletResponseImplTest { 22 | 23 | private HttpServletResponseImpl httpServletResponseImpl; 24 | 25 | @BeforeEach 26 | public void setUp() { 27 | httpServletResponseImpl = new HttpServletResponseImpl(mock(Serializer.class), 28 | mock(Serializer.class), mock(StreamHelper.class), mock(OutputStream.class)); 29 | } 30 | 31 | @Test 32 | public void shouldNotAllowHeadersToBeFlushedTwice() throws IOException { 33 | try { 34 | httpServletResponseImpl.flushHeaders(); 35 | } catch (IllegalStateException e) { 36 | fail("Should not throw exception on the first call"); 37 | } 38 | 39 | assertThrows(IllegalStateException.class, new Executable() { 40 | @Override 41 | public void execute() throws IllegalStateException, IOException { 42 | httpServletResponseImpl.flushHeaders(); 43 | } 44 | }); 45 | } 46 | 47 | @Test 48 | public void shouldRedirectProperly() { 49 | String url = "/SomeUrl"; 50 | httpServletResponseImpl.sendRedirect(url); 51 | assertThat(httpServletResponseImpl.getStatus(), is("HTTP/1.1 301 Moved Permanently")); 52 | assertThat(httpServletResponseImpl.getHeaders().getHeader(Headers.HEADER_LOCATION), is(url)); 53 | } 54 | 55 | @Test 56 | public void shouldNotCreateASinglePrintWriter() { 57 | assertThat(httpServletResponseImpl.getWriter(), is(httpServletResponseImpl.getWriter())); 58 | } 59 | 60 | @Test 61 | public void shouldSetHeadersProperly() { 62 | httpServletResponseImpl.setHeader("StringValue", "value"); 63 | httpServletResponseImpl.setIntHeader("IntValue", 1); 64 | 65 | assertThat(httpServletResponseImpl.getHeaders().getHeader("StringValue"), is("value")); 66 | assertThat(httpServletResponseImpl.getHeaders().getHeader("IntValue"), is("1")); 67 | } 68 | } 69 | // CHECKSTYLE.ON: JavadocType 70 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/protocol/parser/impl/QueryStringParserTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http.protocol.parser.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import ro.polak.http.protocol.parser.MalformedInputException; 8 | import ro.polak.http.protocol.parser.Parser; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | 13 | // CHECKSTYLE.OFF: JavadocType 14 | // CHECKSTYLE.OFF: MagicNumber 15 | public class QueryStringParserTest { 16 | 17 | @Test 18 | public void shouldParseFields() throws MalformedInputException { 19 | String data = "¶m1=ABCD1" 20 | + "¶m2=ABCD2" 21 | + "¶m3=ABC=DEF" 22 | + "¶m4=A%20B%20%3D%20%25%20*"; 23 | 24 | Parser> parser = new QueryStringParser(); 25 | Map parameters = parser.parse(data); 26 | 27 | assertThat(parameters.size(), is(4)); 28 | 29 | assertThat(parameters.get("param1"), is("ABCD1")); 30 | assertThat(parameters.get("param2"), is("ABCD2")); 31 | assertThat(parameters.get("param3"), is("ABC=DEF")); 32 | assertThat(parameters.get("param4"), is("A B = % *")); 33 | } 34 | 35 | @Test 36 | public void shouldParseEmptyFields() throws MalformedInputException { 37 | String data = ""; 38 | Parser> parser = new QueryStringParser(); 39 | Map parameters = parser.parse(data); 40 | 41 | assertThat(parameters.size(), is(0)); 42 | } 43 | 44 | @Test 45 | public void shouldParseNonValidString() throws MalformedInputException { 46 | String data = "/"; 47 | Parser> parser = new QueryStringParser(); 48 | Map parameters = parser.parse(data); 49 | 50 | assertThat(parameters.size(), is(0)); 51 | } 52 | 53 | @Test 54 | public void shouldParseIncompleteFields() throws MalformedInputException { 55 | String data = "=¶m1=" 56 | + "&&" 57 | + "¶m1=" 58 | + "¶m2=ABCD2" 59 | + "¶m3=ABC=DEF" 60 | + "¶m4=A%20B%20%3D%20%25%20*"; 61 | 62 | Parser> parser = new QueryStringParser(); 63 | Map parameters = parser.parse(data); 64 | 65 | assertThat(parameters.size(), is(4)); 66 | 67 | assertThat(parameters.get("param1"), is("")); 68 | assertThat(parameters.get("param2"), is("ABCD2")); 69 | assertThat(parameters.get("param3"), is("ABC=DEF")); 70 | assertThat(parameters.get("param4"), is("A B = % *")); 71 | } 72 | } 73 | // CHECKSTYLE.ON: MagicNumber 74 | // CHECKSTYLE.ON: JavadocType 75 | -------------------------------------------------------------------------------- /http/src/test/java/ro/polak/http/ServiceUnavailableHandlerTest.java: -------------------------------------------------------------------------------- 1 | package ro.polak.http; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.PrintWriter; 8 | import java.net.Socket; 9 | 10 | import ro.polak.http.configuration.ServerConfig; 11 | import ro.polak.http.errorhandler.HttpErrorHandlerResolver; 12 | import ro.polak.http.servlet.factory.HttpServletRequestImplFactory; 13 | import ro.polak.http.servlet.factory.HttpServletResponseImplFactory; 14 | import ro.polak.http.servlet.impl.HttpServletResponseImpl; 15 | 16 | import static org.hamcrest.MatcherAssert.assertThat; 17 | import static org.hamcrest.Matchers.containsString; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.never; 21 | import static org.mockito.Mockito.times; 22 | import static org.mockito.Mockito.verify; 23 | import static org.mockito.Mockito.when; 24 | 25 | // CHECKSTYLE.OFF: JavadocType 26 | public final class ServiceUnavailableHandlerTest { 27 | 28 | private static HttpServletResponseImplFactory factory; 29 | private static ServiceUnavailableHandler serviceUnavailableHandler; 30 | private static ByteArrayOutputStream outputStream; 31 | private static PrintWriter printWriter; 32 | 33 | @BeforeEach 34 | public void setUp() throws Exception { 35 | outputStream = new ByteArrayOutputStream(); 36 | factory = mock(HttpServletResponseImplFactory.class); 37 | HttpServletResponseImpl response = mock(HttpServletResponseImpl.class); 38 | printWriter = new PrintWriter(outputStream); 39 | when(response.getWriter()).thenReturn(printWriter); 40 | 41 | when(factory.createFromSocket(any(Socket.class))).thenReturn(response); 42 | serviceUnavailableHandler = new ServiceUnavailableHandler(factory); 43 | } 44 | 45 | @Test 46 | public void shouldIgnoreRunnableThatIsNotServerRunnable() throws Exception { 47 | serviceUnavailableHandler.rejectedExecution(mock(Runnable.class), null); 48 | verify(factory, never()).createFromSocket(any(Socket.class)); 49 | } 50 | 51 | @Test 52 | public void shouldHandleServerRunnable() throws Exception { 53 | ServerRunnable runnable = new ServerRunnable(mock(Socket.class), mock(ServerConfig.class), 54 | mock(HttpServletRequestImplFactory.class), factory, 55 | mock(HttpErrorHandlerResolver.class), mock(PathHelper.class)); 56 | serviceUnavailableHandler.rejectedExecution(runnable, null); 57 | verify(factory, times(1)).createFromSocket(any(Socket.class)); 58 | printWriter.flush(); 59 | assertThat(outputStream.toString(), containsString("503")); 60 | } 61 | } 62 | // CHECKSTYLE.ON: JavadocType 63 | -------------------------------------------------------------------------------- /http/src/main/java/ro/polak/http/configuration/ServerConfig.java: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * Android Web Server 3 | * Based on JavaLittleWebServer (2008) 4 | *

5 | * Copyright (c) Piotr Polak 2008-2016 6 | **************************************************/ 7 | 8 | package ro.polak.http.configuration; 9 | 10 | import java.util.List; 11 | 12 | import ro.polak.http.MimeTypeMapping; 13 | import ro.polak.http.ServletDispatcher; 14 | import ro.polak.http.resource.provider.ResourceProvider; 15 | 16 | /** 17 | * Server configuration. 18 | * 19 | * @author Piotr Polak piotr [at] polak [dot] ro 20 | * @since 201509 21 | */ 22 | public interface ServerConfig { 23 | 24 | /** 25 | * Returns base path. 26 | * 27 | * @return 28 | */ 29 | String getBasePath(); 30 | 31 | /** 32 | * Returns document root path. 33 | * 34 | * @return 35 | */ 36 | String getDocumentRootPath(); 37 | 38 | /** 39 | * Returns server temp path. 40 | * 41 | * @return 42 | */ 43 | String getTempPath(); 44 | 45 | /** 46 | * Returns the listen port. 47 | * 48 | * @return 49 | */ 50 | int getListenPort(); 51 | 52 | /** 53 | * Returns the mime type mapping. 54 | * 55 | * @return 56 | */ 57 | MimeTypeMapping getMimeTypeMapping(); 58 | 59 | /** 60 | * Returns the number of maximum allowed threads. 61 | * 62 | * @return 63 | */ 64 | int getMaxServerThreads(); 65 | 66 | /** 67 | * Returns whether the server should keep the connections alive. 68 | * 69 | * @return 70 | */ 71 | boolean isKeepAlive(); 72 | 73 | /** 74 | * Returns error 404 file path. 75 | * 76 | * @return 77 | */ 78 | String getErrorDocument404Path(); 79 | 80 | /** 81 | * Returns the error 403 file path. 82 | * 83 | * @return 84 | */ 85 | String getErrorDocument403Path(); 86 | 87 | /** 88 | * Returns the directory index. 89 | * 90 | * @return 91 | */ 92 | List getDirectoryIndex(); 93 | 94 | /** 95 | * Returns an array of supported HTTP methods. 96 | * 97 | * @return 98 | */ 99 | List getSupportedMethods(); 100 | 101 | /** 102 | * Returns available resource providers. 103 | * 104 | * @return 105 | */ 106 | List getResourceProviders(); 107 | 108 | /** 109 | * Returns arbitrary attribute by name. 110 | * 111 | * @param name 112 | * @return 113 | */ 114 | String getAttribute(String name); 115 | 116 | /** 117 | * Returns a servlet dispatcher. 118 | * 119 | * @return 120 | */ 121 | ServletDispatcher getServletDispatcher(); 122 | } 123 | --------------------------------------------------------------------------------

Data received" 41 | + FileUtilities.fileSizeUnits(ro.polak.http.Statistics.getBytesReceived()) + "
Data sent" 45 | + FileUtilities.fileSizeUnits(ro.polak.http.Statistics.getBytesSent()) + "
Requests handled" + ro.polak.http.Statistics.getRequestsHandled() + "
404 errors" + ro.polak.http.Statistics.getError404s() + "
500 errors" + ro.polak.http.Statistics.getError500s() + "