Merge branch 'minSdkVersion-to-android14' into 'master'

The Great Upgrade to minSdkVersion 14!

Closes #1379, #1383, and #248

See merge request fdroid/fdroidclient!676
This commit is contained in:
Hans-Christoph Steiner 2018-04-23 08:16:46 +00:00
commit 8903a089ba
122 changed files with 1868 additions and 4750 deletions

View File

@ -12,7 +12,7 @@ stages:
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
- echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
.test-template: &test-template
artifacts:
@ -54,13 +54,16 @@ errorprone:
- ./gradlew assembleDebug
allow_failure: true
connected10:
connected14:
stage: test
<<: *test-template
variables:
AVD_SDK: "10"
AVD_SDK: "14"
script:
- ./gradlew assembleDebug
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-$AVD_SDK"
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-${AVD_SDK};default;armeabi-v7a"
- echo no | android --verbose create avd --name fcl-test-$AVD_SDK --target android-$AVD_SDK
- emulator64-arm -avd fcl-test-$AVD_SDK -no-skin -no-audio -no-window &
- ./tools/wait-for-emulator
- adb shell input keyevent 82 &

View File

@ -13,24 +13,18 @@ def getVersionName = { ->
return stdout.toString().trim()
}
repositories {
jcenter()
maven {
url "https://jitpack.io"
}
}
dependencies {
compile "com.android.support:support-v4:25.3.1"
compile "com.android.support:appcompat-v7:25.3.1"
compile "com.android.support:gridlayout-v7:25.3.1"
compile "com.android.support:support-annotations:25.3.1"
compile "com.android.support:recyclerview-v7:25.3.1"
compile "com.android.support:cardview-v7:25.3.1"
compile "com.android.support:design:25.3.1"
compile "com.android.support:support-vector-drawable:25.3.1"
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile "com.android.support:palette-v7:25.3.1"
compile "com.android.support:support-v4:27.1.1"
compile "com.android.support:appcompat-v7:27.1.1"
compile "com.android.support:gridlayout-v7:27.1.1"
compile "com.android.support:support-annotations:27.1.1"
compile "com.android.support:recyclerview-v7:27.1.1"
compile "com.android.support:cardview-v7:27.1.1"
compile "com.android.support:design:27.1.1"
compile "com.android.support:support-vector-drawable:27.1.1"
compile 'com.android.support.constraint:constraint-layout:1.1.0'
compile "com.android.support:palette-v7:27.1.1"
compile "com.android.support:preference-v7:27.1.1"
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.google.zxing:core:3.3.2'
@ -41,147 +35,87 @@ dependencies {
compile 'commons-io:commons-io:2.5'
compile 'commons-net:commons-net:3.5'
compile 'org.jmdns:jmdns:3.5.3'
compile 'org.nanohttpd:nanohttpd:2.3.1'
compile 'ch.acra:acra:4.9.1'
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:0.23.0'
compile 'com.hannesdorfmann:adapterdelegates3:3.0.1'
// Migrate this to upstream https://github.com/Ashok-Varma/BottomNavigation if PR #110 gets
// accepted to drop the minSdk to 10.
compile('com.github.pserwylo:BottomNavigation:1.5.0')
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.4'
compile 'com.fasterxml.jackson.core:jackson-core:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
testCompile "org.robolectric:robolectric:3.3.2"
compile 'org.bouncycastle:bcpkix-jdk15on:1.59'
compile 'org.bouncycastle:bcprov-jdk15on:1.59'
testCompile "org.robolectric:robolectric:3.8"
testCompile 'junit:junit:4.12'
// As per https://github.com/robolectric/robolectric/issues/1932#issuecomment-219796474
testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
testCompile "org.mockito:mockito-core:1.10.19"
testCompile "org.mockito:mockito-core:2.7.22"
androidTestCompile "com.android.support:support-annotations:25.3.1"
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}
if (!hasProperty('sourceDeps')) {
repositories {
// This is here until we sort out all dependencies from mavenCentral/jcenter. Once all of
// the dependencies below have been sorted out, this can be removed.
flatDir {
dirs 'libs/binaryDeps'
}
}
dependencies {
compile 'com.madgag.spongycastle:pkix:1.54.0.0'
compile 'com.madgag.spongycastle:prov:1.54.0.0'
compile 'com.madgag.spongycastle:core:1.54.0.0'
// Upstream doesn't have a binary on mavenCentral/jcenter yet:
// https://github.com/kolavar/android-support-v4-preferencefragment/issues/13
compile(name: 'support-v4-preferencefragment-release', ext: 'aar')
// Fork for F-Droid, including support for https. Not merged into upstream
// yet (seems to be a little unsupported as of late), so not using mavenCentral/jcenter.
compile(name: 'nanohttpd-2.1.0')
// Upstream doesn't have a binary on mavenCentral, and it is an SVN repo on
// Google Code. We include this code directly in this repo, and have made
// modifications that should be pushed to anyone who wants to maintain it.
compile(name: 'zipsigner')
}
// Only do the libraries imported from maven repositories. Our own libraries
// (like privileged-api-lib) and the prebuilt jars already checked into the
// source code don't need to be here.
// generate using: `gradle -q calculateChecksums | sort -V`
dependencyVerification {
// generate using: `gradle -q calculateChecksums | sort -V`
dependencyVerification {
verify = [
'android.arch.core:common:d34824b794bc92ff8f647a9bb13a7c73de920de5b47075b5d2c4f0770e9b8bfd',
'android.arch.core:runtime:83400f7575bcfb8a2eeec64e05590f037bfaed1e56aa3a4214d20e55878445e3',
'android.arch.lifecycle:common:614e31cfd33255dc4d5f5d8e62cfa6be2fbbc2a35643a79dc3ed008004c30807',
'android.arch.lifecycle:livedata-core:14e57ff8ffb65a80c7e72d91f2076acccdaf2970f234c6261e03a6127eb5206b',
'android.arch.lifecycle:runtime:094fd793924dd6a5136753e599ac8174a8147f4a401386b694ba7d818c223e2e',
'android.arch.lifecycle:viewmodel:6407c93a5ea9850661dca42a0068d6f3deccefd7228ee69bae1c35d70cbc2557',
'cc.mvdan.accesspoint:library:0837b38adb48b66bb1385adb6ade8ecce7002ad815c55abf13517c82193458ea',
'ch.acra:acra:d2762968c448757a7d6acc9f141881d9632f664988e9723ece33b5f7c79f3bc9',
'commons-io:commons-io:a10418348d234968600ccb1d988efcbbd08716e1d96936ccc1880e7d22513474',
'commons-net:commons-net:c25b0da668b3c5649f002d504def22d1b4cb30d206f05428d2fe168fa1a901c2',
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
'com.android.support:animated-vector-drawable:4bc46edf1946b32d518b41416d1734e915e0cbb28021de3b340527419b070691',
'com.android.support:appcompat-v7:ac1ebbc46589195dda3e0b1becfe410bafd75bdf3edd1cd9acf04850f3895830',
'com.android.support:cardview-v7:defc17032ffa600a82e1c7d96bb574aa5ed64e2b57e28414a245da7d6db0c666',
'com.android.support:design:a3e83064fe99d0a4369f9b46d8bfbe77d0c3022fffdee4be3ac3857b87cc89e3',
'com.android.support:gridlayout-v7:de87a59472f19eb05429faf6b2683a09dd6995f5db562d3daf6033297c312388',
'com.android.support:palette-v7:956276da2ed8b6c087c431da807a496f4908061c9c64d4c9f7b42c626d633662',
'com.android.support:recyclerview-v7:375974a8724e359d97d77fa8522c614f813a3ac4583c1807f154a3f9a054b0a1',
'com.android.support:support-annotations:aedf76962584adfaed2bd3fcaa22406461c4310237fc27e301128edaa2dba2fa',
'com.android.support:support-compat:e02d781268dc60aab6638d8dc20156ea11ca20b962d294b85e6f1e8418cabfa7',
'com.android.support:support-core-ui:6182278a6653e6c111c888963626cbb16f2d0022571cb239760475119e0b92a8',
'com.android.support:support-core-utils:32fac02eb2c20a77fa3e3bc3ede62392a19613f72b8f8e10f5dfa84bb4c89ea1',
'com.android.support:support-fragment:541d6393c1e024453aca2a14f31bea0c7270ff4e2a02783f3528aa426367444d',
'com.android.support:support-media-compat:cbed07d07e0e84fdb4b75712f5fd946229a8af155933c9a92e41db64d00791e0',
'com.android.support:support-v4:07d389154bcf73b47e514964df1578136b26cba78257b8a577a3ccb54beff0ae',
'com.android.support:support-vector-drawable:13728f337f36d1c02d52198a6c20724edb447a0875454d829f95cb9eb4aa293b',
'com.android.support:transition:36c688825a8c0e6e879e18886de83dc90673322822d5b606ee302f70fb558e16',
'com.android.support.constraint:constraint-layout-solver:fcb4c7d705754ca3d69b1b2c3caf445a425599fda8caabbcf855d98ea0663e4e',
'com.android.support.constraint:constraint-layout:d490188709b7bb2f11609beadd7e5eb7538892f308828ec3ff261a74e6ecf47e',
'com.android.support:animated-vector-drawable:59670473f6e98fda792f7bef25dd7292b0a3106031c7a5e30eb020bf26f077bd',
'com.android.support:appcompat-v7:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335',
'com.android.support:cardview-v7:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886',
'com.android.support:design:7225973f7ee03765008a9c2f17a40b154c6885169fef022276e811c926a2202c',
'com.android.support:gridlayout-v7:2f5af33c4be1d3e4e3fa999323265718ac1a4c81df4c0373d6ce8901613b1671',
'com.android.support:palette-v7:6d24037fb375c7884f878edeb88c812b87a05c69221513507ecea21c257d6314',
'com.android.support:preference-v7:a1798a826b4097d00e49280f412b21af08f9bf1179c2e3838dc339d9f843416d',
'com.android.support:recyclerview-v7:d735e4727878e99ef3980c10d15dc3468462fd509d4fb60cb8bd20b0f735085c',
'com.android.support:support-annotations:3365960206c3d2b09e845f555e7f88f8effc8d2f00b369e66c4be384029299cf',
'com.android.support:support-compat:880ce01ff5be42b233ff8ec0c61cefb7dc3dc9500fea9e24423214813ac27ea2',
'com.android.support:support-core-ui:a3ae20e6d5dffba69ac97b99846d2738003af8563843d5f3c9dc4c35b4804241',
'com.android.support:support-core-utils:61036832c54e8701aae954fc3bf96d1d80bf8d9dd531bff77d72def456ba087a',
'com.android.support:support-fragment:ec72d6ac36a1a0e6523bbddba33d73ffad070b9b3dd246cc44d8727a41ddb5e6',
'com.android.support:support-media-compat:55e9837dda88b74a8c812c63a78c63fd83c6c039a8c22d318492663a493585eb',
'com.android.support:support-v4:4f41dfc3e89f2738e45c86264a85c0934d055ee8ebe2020e23c97f303b80a48b',
'com.android.support:support-vector-drawable:1c0f421114cf4627cf208776d6eb4f76340c78b7e96fe6e12b3e6eb950caf1b9',
'com.android.support:transition:c0765b2f3c78696567ec5b3f519d22da1e3df11ac994625adf4bb4dc571caacc',
'com.ashokvarma.android:bottom-navigation-bar:f18d740e1777927ad761349298b5d4981cd9f6d2abe70f505abf415ae069baaa',
'com.fasterxml.jackson.core:jackson-annotations:6b7802f6c22c09c4a92a2ebeb76e755c3c0a58dfbf419835fae470d89e469b86',
'com.fasterxml.jackson.core:jackson-core:256ff34118ab292d1b4f3ee4d2c3e5e5f0f609d8e07c57e8ad1f51c46d4fbb46',
'com.fasterxml.jackson.core:jackson-databind:4f74337b6d18664be0f5b15c6664b17aa3972c9c175092328b139b894ff66f19',
'com.github.pserwylo:BottomNavigation:83d7941a7a8d21ba1a8a708cd683b1bb07c6cf898044dc92eadf18a7a7d54f90',
'com.google.zxing:core:52dd6211bbaf4e600de693834d597e49707f3e6606e1f5d3740fbb8274466abe',
'com.hannesdorfmann:adapterdelegates3:1b20d099d6e7afe57aceca13b713b386959d94a247c3c06a7aeb65b866ece02f',
'com.madgag.spongycastle:core:1e7fa4b19ccccd1011364ab838d0b4702470c178bbbdd94c5c90b2d4d749ea1e',
'com.madgag.spongycastle:pkix:721a302f5ce18bf6fff89d514ef224c37b5dd9ca67a16b56fafaea4b24a51482',
'com.madgag.spongycastle:prov:cf89c550fda86c0f26858c3d851ac1d2ce49cd78dd144cd86f307b7ea3e6afd7',
'com.nostra13.universalimageloader:universal-image-loader:dbd5197ffec3a8317533190870a7c00ff3750dd6a31241448c6a5522d51b65b4',
'eu.chainfire:libsuperuser:018344ff19ee94d252c14b4a503ee8b519184db473a5af83513f5837c413b128',
'info.guardianproject.netcipher:netcipher:eeeb5d0d95ccfe176b4296cbd71a9a24c6efb0bab5c4025a8c6bc36abdddfc75',
'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
'io.reactivex:rxandroid:35c1a90f8c1f499db3c1f3d608e1f191ac8afddb10c02dd91ef04c03a0a4bcda',
'io.reactivex:rxjava:2c162afd78eba217cdfee78b60e85d3bfb667db61e12bc95e3cf2ddc5beeadf6',
'org.bouncycastle:bcpkix-jdk15on:601d85cfbcef76a1cb77cbf755a6234a4ba1d4c02a98d9a81028d471f388694f',
'org.bouncycastle:bcprov-jdk15on:1c31e44e331d25e46d293b3e8ee2d07028a67db011e74cb2443285aed1d59c85',
'org.jmdns:jmdns:24e7e3a50a579136400e8c9b0750399eb3c7558918bdf52c0ffa5e0fa5aad503',
'org.nanohttpd:nanohttpd:de864c47818157141a24c9acb36df0c47d7bf15b7ff48c90610f3eb4e5df0e58',
'org.slf4j:slf4j-api:e56288031f5e60652c06e7bb6e9fa410a61231ab54890f7b708fc6adc4107c5b',
]
}
} else {
logger.info "Setting up *source* dependencies for F-Droid (because you passed in the -PsourceDeps argument to gradle while building)."
dependencies {
compile(project(':extern:support-v4-preferencefragment')) {
exclude module: 'support-v4'
}
compile project(':extern:nanohttpd:core')
compile project(':extern:zipsigner')
}
task binaryDeps(type: Copy, dependsOn: ':app:prepareReleaseDependencies') {
enabled = project.hasProperty('sourceDeps')
description = "Copies .jar and .aar files from subproject dependencies in extern/ to app/libs. Requires the sourceDeps property to be set (\"gradle -PsourceDeps binaryDeps\")"
from('../extern/') {
include 'support-v4-preferencefragment/build/outputs/aar/support-v4-preferencefragment-release.aar'
include 'nanohttpd/core/build/libs/nanohttpd-2.1.0.jar'
include 'zipsigner/build/libs/zipsigner.jar'
}
into 'libs/binaryDeps'
includeEmptyDirs false
eachFile { FileCopyDetails details ->
// Don't copy to a sub folder such as libs/binaryDeps/Project/build/outputs/aar/project.aar, but
// rather libs/binaryDeps/project.aar.
details.path = details.name
}
}
}
def isCi = "true".equals(System.getenv("CI"))
def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true"))
android {
compileSdkVersion 24
buildToolsVersion '25.0.3'
compileSdkVersion 27
buildToolsVersion '27.0.3'
buildTypes {
// use proguard on debug too since we have unknowingly broken
@ -258,6 +192,7 @@ android {
testOptions {
unitTests {
includeAndroidResources = true
// prevent tests from dying on android.util.Log calls
returnDefaultValues = true
all {
@ -324,6 +259,7 @@ task pmdMain(type: Pmd) {
ruleSets = [] // otherwise defaults clash with the list in rules.xml
source 'src/main/java'
include '**/*.java'
exclude '**/kellinwood/**/*.java'
}
task pmdTest(type: Pmd) {

Binary file not shown.

View File

@ -11,6 +11,8 @@
<!-- These are important to us, so promote from warning to error -->
<issue id="UnusedResources" severity="error">
<ignore path="src/main/res/drawable/category_**.png" />
<ignore path="src/main/res/values/dimens.xml"/>
<ignore path="src/main/res/values/styles.xml"/>
</issue>
<issue id="AppCompatMethod" severity="error"/>
<issue id="NestedScrolling" severity="error"/>
@ -31,6 +33,10 @@
<ignore path="src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java"/>
</issue>
<issue id="ProtectedPermissions" severity="error">
<ignore path="src/main/AndroidManifest.xml"/>
</issue>
<!-- these should be fixed, but it'll be a chunk of work -->
<issue id="SetTextI18n" severity="error">
<ignore path="src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java"/>

View File

@ -22,7 +22,7 @@
# removed, proguard will strip classes which are required, which may result in
# crashes.
-keep class kellinwood.security.zipsigner.** {*;}
-keep class org.spongycastle.** {*;}
-keep class org.bouncycastle.** {*;}
# This keeps class members used for SystemInstaller IPC.
# Reference: https://gitlab.com/fdroid/fdroidclient/issues/79

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.fdroid.fdroid"
android:installLocation="auto">
<uses-sdk
android:minSdkVersion="10"
android:minSdkVersion="14"
android:targetSdkVersion="24"
/>
@ -150,13 +151,6 @@
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".views.main.MainActivity"/>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.org.fdroid.fdroid.repo"/>
</intent-filter>
</activity>
<activity
android:name=".NfcNotEnabledActivity"
@ -456,9 +450,9 @@
character set, making for more compact QR Codes.
-->
<data android:scheme="http"/>
<data android:scheme="HTTP"/>
<data android:scheme="HTTP" tools:ignore="AppLinkUrlError"/>
<data android:scheme="https"/>
<data android:scheme="HTTPS"/>
<data android:scheme="HTTPS" tools:ignore="AppLinkUrlError"/>
<data android:host="*"/>
@ -515,9 +509,9 @@
character set, making for more compact QR Codes.
-->
<data android:scheme="fdroidrepo"/>
<data android:scheme="FDROIDREPO"/>
<data android:scheme="FDROIDREPO" tools:ignore="AppLinkUrlError"/>
<data android:scheme="fdroidrepos"/>
<data android:scheme="FDROIDREPOS"/>
<data android:scheme="FDROIDREPOS" tools:ignore="AppLinkUrlError"/>
</intent-filter>

View File

@ -1,100 +0,0 @@
/*
* *
* * This file is part of QuickLyric
* * Created by geecko
* *
* * QuickLyric is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * QuickLyric is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* * You should have received a copy of the GNU General Public License
* * along with QuickLyric. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.geecko.QuickLyric.view;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.PreferenceManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialog;
import android.util.AttributeSet;
import java.lang.reflect.Method;
public class AppCompatListPreference extends ListPreference {
private AppCompatDialog appCompatDialog;
public AppCompatListPreference(Context context) {
super(context);
}
public AppCompatListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public AppCompatDialog getDialog() {
return appCompatDialog;
}
@Override
protected void showDialog(Bundle state) {
if (getEntries() == null || getEntryValues() == null) {
throw new IllegalStateException(
"ListPreference requires an entries array and an entryValues array.");
}
int preselect = findIndexOfValue(getValue());
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setTitle(getDialogTitle())
.setIcon(getDialogIcon())
.setSingleChoiceItems(getEntries(), preselect, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which >= 0 && getEntryValues() != null) {
String value = getEntryValues()[which].toString();
if (callChangeListener(value) && isPersistent()) {
setValue(value);
}
}
dialog.dismiss();
}
});
PreferenceManager pm = getPreferenceManager();
try {
Method method = pm.getClass().getDeclaredMethod(
"registerOnActivityDestroyListener",
PreferenceManager.OnActivityDestroyListener.class);
method.setAccessible(true);
method.invoke(pm, this);
} catch (Exception e) {
e.printStackTrace();
}
appCompatDialog = builder.create();
if (state != null) {
appCompatDialog.onRestoreInstanceState(state);
}
appCompatDialog.show();
}
@Override
public void onActivityDestroy() {
super.onActivityDestroy();
if (appCompatDialog != null && appCompatDialog.isShowing() &&
appCompatDialog.getWindow() != null && appCompatDialog.getWindow().getWindowManager() != null) {
appCompatDialog.dismiss();
}
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2010 Ken Ellinwood.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.text.SimpleDateFormat;
import java.util.Date;
public abstract class AbstractLogger implements LoggerInterface {
protected String category;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public AbstractLogger(String category) {
this.category = category;
}
protected String format(String level, String message) {
return String.format("%s %s %s: %s\n", dateFormat.format(new Date()), level, category, message);
}
protected abstract void write(String level, String message, Throwable t);
protected void writeFixNullMessage(String level, String message, Throwable t) {
if (message == null) {
if (t != null) message = t.getClass().getName();
else message = "null";
}
write(level, message, t);
}
public void debug(String message, Throwable t) {
writeFixNullMessage(DEBUG, message, t);
}
public void debug(String message) {
writeFixNullMessage(DEBUG, message, null);
}
public void error(String message, Throwable t) {
writeFixNullMessage(ERROR, message, t);
}
public void error(String message) {
writeFixNullMessage(ERROR, message, null);
}
public void info(String message, Throwable t) {
writeFixNullMessage(INFO, message, t);
}
public void info(String message) {
writeFixNullMessage(INFO, message, null);
}
public void warning(String message, Throwable t) {
writeFixNullMessage(WARNING, message, t);
}
public void warning(String message) {
writeFixNullMessage(WARNING, message, null);
}
public boolean isDebugEnabled() {
return true;
}
public boolean isErrorEnabled() {
return true;
}
public boolean isInfoEnabled() {
return true;
}
public boolean isWarningEnabled() {
return true;
}
}

View File

@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public class ConsoleLoggerFactory implements LoggerFactory {
public LoggerInterface getLogger(String category) {
return new StreamLogger( category, System.out);
return new StreamLogger(category, System.out);
}
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public interface LoggerFactory {
public LoggerInterface getLogger( String category);
public LoggerInterface getLogger(String category);
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2010 Ken Ellinwood.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public interface LoggerInterface {
public static final String ERROR = "ERROR";
public static final String WARNING = "WARNING";
public static final String INFO = "INFO";
public static final String DEBUG = "DEBUG";
public boolean isErrorEnabled();
public void error(String message);
public void error(String message, Throwable t);
public boolean isWarningEnabled();
public void warning(String message);
public void warning(String message, Throwable t);
public boolean isInfoEnabled();
public void info(String message);
public void info(String message, Throwable t);
public boolean isDebugEnabled();
public void debug(String message);
public void debug(String message, Throwable t);
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.util.Map;
@ -22,18 +23,18 @@ public class LoggerManager {
static LoggerFactory factory = new NullLoggerFactory();
static Map<String,LoggerInterface> loggers = new TreeMap<String,LoggerInterface>();
static Map<String, LoggerInterface> loggers = new TreeMap<String, LoggerInterface>();
public static void setLoggerFactory( LoggerFactory f) {
public static void setLoggerFactory(LoggerFactory f) {
factory = f;
}
public static LoggerInterface getLogger(String category) {
LoggerInterface logger = loggers.get( category);
LoggerInterface logger = loggers.get(category);
if (logger == null) {
logger = factory.getLogger(category);
loggers.put( category, logger);
loggers.put(category, logger);
}
return logger;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2010 Ken Ellinwood.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
public class NullLoggerFactory implements LoggerFactory {
static LoggerInterface logger = new LoggerInterface() {
public void debug(String message) {
}
public void debug(String message, Throwable t) {
}
public void error(String message) {
}
public void error(String message, Throwable t) {
}
public void info(String message) {
}
public void info(String message, Throwable t) {
}
public boolean isDebugEnabled() {
return false;
}
public boolean isErrorEnabled() {
return false;
}
public boolean isInfoEnabled() {
return false;
}
public boolean isWarningEnabled() {
return false;
}
public void warning(String message) {
}
public void warning(String message, Throwable t) {
}
};
public LoggerInterface getLogger(String category) {
return logger;
}
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.logging;
import java.io.PrintStream;
@ -21,15 +22,14 @@ public class StreamLogger extends AbstractLogger {
PrintStream out;
public StreamLogger( String category, PrintStream out)
{
super( category);
public StreamLogger(String category, PrintStream out) {
super(category);
this.out = out;
}
@Override
protected void write(String level, String message, Throwable t) {
out.print( format( level, message));
out.print(format(level, message));
if (t != null) t.printStackTrace(out);
}

View File

@ -1,14 +1,15 @@
package kellinwood.security.zipsigner;
public class AutoKeyException extends RuntimeException {
private static final long serialVersionUID = 1L;
public AutoKeyException( String message) {
public AutoKeyException(String message) {
super(message);
}
public AutoKeyException( String message, Throwable cause) {
public AutoKeyException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -15,15 +15,16 @@
*/
package kellinwood.security.zipsigner;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/*
* This class provides Base64 encoding services using one of several possible
* implementations available elsewhere in the classpath. Supported implementations
@ -55,16 +56,16 @@ public class Base64 {
Class<Object> clazz;
logger = LoggerManager.getLogger( Base64.class.getName());
logger = LoggerManager.getLogger(Base64.class.getName());
try {
clazz = (Class<Object>) Class.forName("android.util.Base64");
// Looking for encode( byte[] input, int flags)
aEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE);
aDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE);
logger.info( clazz.getName() + " is available.");
}
catch (ClassNotFoundException x) {} // Ignore
logger.info(clazz.getName() + " is available.");
} catch (ClassNotFoundException x) {
} // Ignore
catch (Exception x) {
logger.error("Failed to initialize use of android.util.Base64", x);
}
@ -74,12 +75,12 @@ public class Base64 {
bEncoder = clazz.newInstance();
// Looking for encode( byte[] input, int offset, int length, OutputStream output)
bEncodeMethod = clazz.getMethod("encode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
logger.info( clazz.getName() + " is available.");
logger.info(clazz.getName() + " is available.");
// Looking for decode( byte[] input, int offset, int length, OutputStream output)
bDecodeMethod = clazz.getMethod("decode", byte[].class, Integer.TYPE, Integer.TYPE, OutputStream.class);
}
catch (ClassNotFoundException x) {} // Ignore
} catch (ClassNotFoundException x) {
} // Ignore
catch (Exception x) {
logger.error("Failed to initialize use of org.bouncycastle.util.encoders.Base64Encoder", x);
}
@ -89,42 +90,38 @@ public class Base64 {
}
public static String encode( byte[] data) {
public static String encode(byte[] data) {
try {
if (aEncodeMethod != null) {
// Invoking a static method call, using null for the instance value
byte[] encodedBytes = (byte[])aEncodeMethod.invoke(null, data, 2);
return new String( encodedBytes);
}
else if (bEncodeMethod != null) {
byte[] encodedBytes = (byte[]) aEncodeMethod.invoke(null, data, 2);
return new String(encodedBytes);
} else if (bEncodeMethod != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bEncodeMethod.invoke(bEncoder, data, 0, data.length, baos);
return new String( baos.toByteArray());
return new String(baos.toByteArray());
}
}
catch (Exception x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (Exception x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}
throw new IllegalStateException("No base64 encoder implementation is available.");
}
public static byte[] decode( byte[] data) {
public static byte[] decode(byte[] data) {
try {
if (aDecodeMethod != null) {
// Invoking a static method call, using null for the instance value
byte[] decodedBytes = (byte[])aDecodeMethod.invoke(null, data, 2);
byte[] decodedBytes = (byte[]) aDecodeMethod.invoke(null, data, 2);
return decodedBytes;
}
else if (bDecodeMethod != null) {
} else if (bDecodeMethod != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bDecodeMethod.invoke(bEncoder, data, 0, data.length, baos);
return baos.toByteArray();
}
}
catch (Exception x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (Exception x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}

View File

@ -15,58 +15,58 @@
*/
package kellinwood.security.zipsigner;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/** Produces the classic hex dump with an address column, hex data
/**
* Produces the classic hex dump with an address column, hex data
* section (16 bytes per row) and right-column printable character dislpay.
*/
public class HexDumpEncoder
{
public class HexDumpEncoder {
static HexEncoder encoder = new HexEncoder();
public static String encode( byte[] data) {
public static String encode(byte[] data) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
encoder.encode( data, 0, data.length, baos);
encoder.encode(data, 0, data.length, baos);
byte[] hex = baos.toByteArray();
StringBuilder hexDumpOut = new StringBuilder();
for (int i = 0; i < hex.length; i += 32) {
int max = Math.min(i+32, hex.length);
int max = Math.min(i + 32, hex.length);
StringBuilder hexOut = new StringBuilder();
StringBuilder chrOut = new StringBuilder();
hexOut.append( String.format("%08x: ", (i/2)));
hexOut.append(String.format("%08x: ", (i / 2)));
for (int j = i; j < max; j+= 2) {
hexOut.append( Character.valueOf( (char)hex[j]));
hexOut.append( Character.valueOf( (char)hex[j+1]));
if ((j+2) % 4 == 0) hexOut.append( ' ');
for (int j = i; j < max; j += 2) {
hexOut.append(Character.valueOf((char) hex[j]));
hexOut.append(Character.valueOf((char) hex[j + 1]));
if ((j + 2) % 4 == 0) hexOut.append(' ');
int dataChar = data[j/2];
if (dataChar >= 32 && dataChar < 127) chrOut.append( Character.valueOf( (char)dataChar));
else chrOut.append( '.');
int dataChar = data[j / 2];
if (dataChar >= 32 && dataChar < 127) chrOut.append(Character.valueOf((char) dataChar));
else chrOut.append('.');
}
hexDumpOut.append( hexOut.toString());
hexDumpOut.append(hexOut.toString());
for (int k = hexOut.length(); k < 50; k++) hexDumpOut.append(' ');
hexDumpOut.append( " ");
hexDumpOut.append( chrOut);
hexDumpOut.append(" ");
hexDumpOut.append(chrOut);
hexDumpOut.append("\n");
}
return hexDumpOut.toString();
}
catch (IOException x) {
throw new IllegalStateException( x.getClass().getName() + ": " + x.getMessage());
} catch (IOException x) {
throw new IllegalStateException(x.getClass().getName() + ": " + x.getMessage());
}
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner;
/*
@ -32,12 +33,11 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import java.io.IOException;
import java.io.OutputStream;
public class HexEncoder
{
public class HexEncoder {
protected final byte[] encodingTable =
{
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
/*
@ -45,11 +45,9 @@ public class HexEncoder
*/
protected final byte[] decodingTable = new byte[128];
protected void initialiseDecodingTable()
{
for (int i = 0; i < encodingTable.length; i++)
{
decodingTable[encodingTable[i]] = (byte)i;
protected void initialiseDecodingTable() {
for (int i = 0; i < encodingTable.length; i++) {
decodingTable[encodingTable[i]] = (byte) i;
}
decodingTable['A'] = decodingTable['a'];
@ -60,8 +58,7 @@ public class HexEncoder
decodingTable['F'] = decodingTable['f'];
}
public HexEncoder()
{
public HexEncoder() {
initialiseDecodingTable();
}
@ -75,10 +72,8 @@ public class HexEncoder
int off,
int length,
OutputStream out)
throws IOException
{
for (int i = off; i < (off + length); i++)
{
throws IOException {
for (int i = off; i < (off + length); i++) {
int v = data[i] & 0xff;
out.write(encodingTable[(v >>> 4)]);
@ -89,9 +84,8 @@ public class HexEncoder
}
private boolean ignore(
char c)
{
return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
char c) {
return (c == '\n' || c == '\r' || c == '\t' || c == ' ');
}
/**
@ -105,17 +99,14 @@ public class HexEncoder
int off,
int length,
OutputStream out)
throws IOException
{
throws IOException {
byte b1, b2;
int outLen = 0;
int end = off + length;
while (end > off)
{
if (!ignore((char)data[end - 1]))
{
while (end > off) {
if (!ignore((char) data[end - 1])) {
break;
}
@ -123,17 +114,14 @@ public class HexEncoder
}
int i = off;
while (i < end)
{
while (i < end && ignore((char)data[i]))
{
while (i < end) {
while (i < end && ignore((char) data[i])) {
i++;
}
b1 = decodingTable[data[i++]];
while (i < end && ignore((char)data[i]))
{
while (i < end && ignore((char) data[i])) {
i++;
}
@ -156,17 +144,14 @@ public class HexEncoder
public int decode(
String data,
OutputStream out)
throws IOException
{
throws IOException {
byte b1, b2;
int length = 0;
int end = data.length();
while (end > 0)
{
if (!ignore(data.charAt(end - 1)))
{
while (end > 0) {
if (!ignore(data.charAt(end - 1))) {
break;
}
@ -174,17 +159,14 @@ public class HexEncoder
}
int i = 0;
while (i < end)
{
while (i < end && ignore(data.charAt(i)))
{
while (i < end) {
while (i < end && ignore(data.charAt(i))) {
i++;
}
b1 = decodingTable[data.charAt(i++)];
while (i < end && ignore(data.charAt(i)))
{
while (i < end && ignore(data.charAt(i))) {
i++;
}

View File

@ -1,5 +1,3 @@
/*
* Copyright (C) 2010 Ken Ellinwood
*
@ -15,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import java.security.PrivateKey;
@ -38,16 +37,14 @@ public class KeySet {
public KeySet() {
}
public KeySet( String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate)
{
public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] sigBlockTemplate) {
this.name = name;
this.publicKey = publicKey;
this.privateKey = privateKey;
this.sigBlockTemplate = sigBlockTemplate;
}
public KeySet( String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate)
{
public KeySet(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] sigBlockTemplate) {
this.name = name;
this.publicKey = publicKey;
this.privateKey = privateKey;

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
public class ProgressEvent {
@ -27,18 +28,23 @@ public class ProgressEvent {
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getPercentDone() {
return percentDone;
}
public void setPercentDone(int percentDone) {
this.percentDone = percentDone;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import java.util.ArrayList;
@ -23,8 +24,7 @@ public class ProgressHelper {
private int progressCurrentItem = 0;
private ProgressEvent progressEvent = new ProgressEvent();
public void initProgress()
{
public void initProgress() {
progressTotalItems = 10000;
progressCurrentItem = 0;
}
@ -45,7 +45,7 @@ public class ProgressHelper {
this.progressCurrentItem = progressCurrentItem;
}
public void progress( int priority, String message) {
public void progress(int priority, String message) {
progressCurrentItem += 1;
@ -58,24 +58,22 @@ public class ProgressHelper {
progressEvent.setMessage(message);
progressEvent.setPercentDone(percentDone);
progressEvent.setPriority(priority);
listener.onProgress( progressEvent);
listener.onProgress(progressEvent);
}
}
private ArrayList<ProgressListener> listeners = new ArrayList<ProgressListener>();
@SuppressWarnings("unchecked")
public synchronized void addProgressListener( ProgressListener l)
{
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
public synchronized void addProgressListener(ProgressListener l) {
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
list.add(l);
listeners = list;
}
@SuppressWarnings("unchecked")
public synchronized void removeProgressListener( ProgressListener l)
{
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>)listeners.clone();
public synchronized void removeProgressListener(ProgressListener l) {
ArrayList<ProgressListener> list = (ArrayList<ProgressListener>) listeners.clone();
list.remove(l);
listeners = list;
}

View File

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
public interface ProgressListener {
/** Called to notify the listener that progress has been made during
the zip signing operation.
/**
* Called to notify the listener that progress has been made during
* the zip signing operation.
*/
public void onProgress( ProgressEvent event);
public void onProgress(ProgressEvent event);
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner;
/**
@ -14,7 +15,9 @@ public interface ResourceAdapter {
GENERATING_SIGNATURE_FILE,
GENERATING_SIGNATURE_BLOCK,
COPYING_ZIP_ENTRY
};
}
public String getString( Item item, Object... args);
;
public String getString(Item item, Object... args);
}

View File

@ -13,59 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.security.zipsigner;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@SuppressWarnings("restriction")
public class ZipSignature {
byte[] beforeAlgorithmIdBytes = { 0x30, 0x21 };
byte[] beforeAlgorithmIdBytes = {0x30, 0x21};
// byte[] algorithmIdBytes;
// algorithmIdBytes = sun.security.x509.AlgorithmId.get("SHA1").encode();
byte[] algorithmIdBytes = {0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00 };
byte[] algorithmIdBytes = {0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00};
byte[] afterAlgorithmIdBytes = { 0x04, 0x14 };
byte[] afterAlgorithmIdBytes = {0x04, 0x14};
Cipher cipher;
MessageDigest md;
public ZipSignature() throws IOException, GeneralSecurityException
{
public ZipSignature() throws IOException, GeneralSecurityException {
md = MessageDigest.getInstance("SHA1");
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
}
public void initSign( PrivateKey privateKey) throws InvalidKeyException
{
public void initSign(PrivateKey privateKey) throws InvalidKeyException {
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
}
public void update( byte[] data) {
md.update( data);
public void update(byte[] data) {
md.update(data);
}
public void update( byte[] data, int offset, int count) {
md.update( data, offset, count);
public void update(byte[] data, int offset, int count) {
md.update(data, offset, count);
}
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException
{
cipher.update( beforeAlgorithmIdBytes);
cipher.update( algorithmIdBytes);
cipher.update( afterAlgorithmIdBytes);
cipher.update( md.digest());
public byte[] sign() throws BadPaddingException, IllegalBlockSizeException {
cipher.update(beforeAlgorithmIdBytes);
cipher.update(algorithmIdBytes);
cipher.update(afterAlgorithmIdBytes);
cipher.update(md.digest());
return cipher.doFinal();
}
}

View File

@ -26,6 +26,7 @@
* using signature block template files.
*/
package kellinwood.security.zipsigner;
import kellinwood.logging.LoggerInterface;
@ -38,17 +39,40 @@ import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.*;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@ -58,11 +82,10 @@ import java.util.regex.Pattern;
* This is a modified copy of com.android.signapk.SignApk.java. It provides an
* API to sign JAR files (including APKs and Zip/OTA updates) in
* a way compatible with the mincrypt verifier, using SHA1 and RSA keys.
*
* <p>
* Please see the README.txt file in the root of this project for usage instructions.
*/
public class ZipSigner
{
public class ZipSigner {
private boolean canceled = false;
@ -78,11 +101,11 @@ public class ZipSigner
private static Pattern stripPattern =
Pattern.compile("^META-INF/(.*)[.](SF|RSA|DSA)$");
Map<String,KeySet> loadedKeys = new HashMap<String,KeySet>();
Map<String, KeySet> loadedKeys = new HashMap<String, KeySet>();
KeySet keySet = null;
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( ZipSigner.class.getName());
if (log == null) log = LoggerManager.getLogger(ZipSigner.class.getName());
return log;
}
@ -94,21 +117,20 @@ public class ZipSigner
// Allowable key modes.
public static final String[] SUPPORTED_KEY_MODES =
new String[] { MODE_AUTO_TESTKEY, MODE_AUTO, MODE_AUTO_NONE, "media", "platform", "shared", KEY_TESTKEY, KEY_NONE};
new String[]{MODE_AUTO_TESTKEY, MODE_AUTO, MODE_AUTO_NONE, "media", "platform", "shared", KEY_TESTKEY, KEY_NONE};
String keymode = KEY_TESTKEY; // backwards compatible with versions that only signed with this key
Map<String,String> autoKeyDetect = new HashMap<String,String>();
Map<String, String> autoKeyDetect = new HashMap<String, String>();
AutoKeyObservable autoKeyObservable = new AutoKeyObservable();
public ZipSigner() throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
public ZipSigner() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// MD5 of the first 1458 bytes of the signature block generated by the key, mapped to the key name
autoKeyDetect.put( "aa9852bc5a53272ac8031d49b65e4b0e", "media");
autoKeyDetect.put( "e60418c4b638f20d0721e115674ca11f", "platform");
autoKeyDetect.put( "3e24e49741b60c215c010dc6048fca7d", "shared");
autoKeyDetect.put( "dab2cead827ef5313f28e22b6fa8479f", "testkey");
autoKeyDetect.put("aa9852bc5a53272ac8031d49b65e4b0e", "media");
autoKeyDetect.put("e60418c4b638f20d0721e115674ca11f", "platform");
autoKeyDetect.put("3e24e49741b60c215c010dc6048fca7d", "shared");
autoKeyDetect.put("dab2cead827ef5313f28e22b6fa8479f", "testkey");
}
@ -121,7 +143,7 @@ public class ZipSigner
}
// when the key mode is automatic, the observers are called when the key is determined
public void addAutoKeyObserver( Observer o) {
public void addAutoKeyObserver(Observer o) {
autoKeyObservable.addObserver(o);
}
@ -129,16 +151,14 @@ public class ZipSigner
return keymode;
}
public void setKeymode(String km) throws IOException, GeneralSecurityException
{
public void setKeymode(String km) throws IOException, GeneralSecurityException {
if (getLogger().isDebugEnabled()) getLogger().debug("setKeymode: " + km);
keymode = km;
if (keymode.startsWith(MODE_AUTO)) {
keySet = null;
}
else {
} else {
progressHelper.initProgress();
loadKeys( keymode);
loadKeys(keymode);
}
}
@ -147,9 +167,8 @@ public class ZipSigner
}
protected String autoDetectKey( String mode, Map<String,ZioEntry> zioEntries)
throws NoSuchAlgorithmException, IOException
{
protected String autoDetectKey(String mode, Map<String, ZioEntry> zioEntries)
throws NoSuchAlgorithmException, IOException {
boolean debug = getLogger().isDebugEnabled();
if (!mode.startsWith(MODE_AUTO)) return mode;
@ -158,7 +177,7 @@ public class ZipSigner
// Auto-determine which keys to use
String keyName = null;
// Start by finding the signature block file in the input.
for (Map.Entry<String,ZioEntry> entry : zioEntries.entrySet()) {
for (Map.Entry<String, ZioEntry> entry : zioEntries.entrySet()) {
String entryName = entry.getKey();
if (entryName.startsWith("META-INF/") && entryName.endsWith(".RSA")) {
@ -167,18 +186,18 @@ public class ZipSigner
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] entryData = entry.getValue().getData();
if (entryData.length < 1458) break; // sig block too short to be a supported key
md5.update( entryData, 0, 1458);
md5.update(entryData, 0, 1458);
byte[] rawDigest = md5.digest();
// Create the hex representation of the digest value
StringBuilder builder = new StringBuilder();
for( byte b : rawDigest) {
builder.append( String.format("%02x", b));
for (byte b : rawDigest) {
builder.append(String.format("%02x", b));
}
String md5String = builder.toString();
// Lookup the key name
keyName = autoKeyDetect.get( md5String);
keyName = autoKeyDetect.get(md5String);
if (debug) {
@ -192,13 +211,12 @@ public class ZipSigner
}
}
if (mode.equals( MODE_AUTO_TESTKEY)) {
if (mode.equals(MODE_AUTO_TESTKEY)) {
// in auto-testkey mode, fallback to the testkey if it couldn't be determined
if (debug) getLogger().debug("Falling back to key="+ keyName);
if (debug) getLogger().debug("Falling back to key=" + keyName);
return KEY_TESTKEY;
}
else if (mode.equals(MODE_AUTO_NONE)) {
} else if (mode.equals(MODE_AUTO_NONE)) {
// in auto-node mode, simply copy the input to the output when the key can't be determined.
if (debug) getLogger().debug("Unable to determine key, returning: " + KEY_NONE);
return KEY_NONE;
@ -212,44 +230,41 @@ public class ZipSigner
}
// Loads one of the built-in keys (media, platform, shared, testkey)
public void loadKeys( String name)
throws IOException, GeneralSecurityException
{
public void loadKeys(String name)
throws IOException, GeneralSecurityException {
keySet = loadedKeys.get(name);
if (keySet != null) return;
keySet = new KeySet();
keySet.setName(name);
loadedKeys.put( name, keySet);
loadedKeys.put(name, keySet);
if (KEY_NONE.equals(name)) return;
issueLoadingCertAndKeysProgressEvent();
// load the private key
URL privateKeyUrl = getClass().getResource("/keys/"+name+".pk8");
URL privateKeyUrl = getClass().getResource("/keys/" + name + ".pk8");
keySet.setPrivateKey(readPrivateKey(privateKeyUrl, null));
// load the certificate
URL publicKeyUrl = getClass().getResource("/keys/"+name+".x509.pem");
URL publicKeyUrl = getClass().getResource("/keys/" + name + ".x509.pem");
keySet.setPublicKey(readPublicKey(publicKeyUrl));
// load the signature block template
URL sigBlockTemplateUrl = getClass().getResource("/keys/"+name+".sbt");
URL sigBlockTemplateUrl = getClass().getResource("/keys/" + name + ".sbt");
if (sigBlockTemplateUrl != null) {
keySet.setSigBlockTemplate(readContentAsBytes(sigBlockTemplateUrl));
}
}
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate)
{
keySet = new KeySet( name, publicKey, privateKey, signatureBlockTemplate);
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, byte[] signatureBlockTemplate) {
keySet = new KeySet(name, publicKey, privateKey, signatureBlockTemplate);
}
public void setKeys( String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate)
{
keySet = new KeySet( name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
public void setKeys(String name, X509Certificate publicKey, PrivateKey privateKey, String signatureAlgorithm, byte[] signatureBlockTemplate) {
keySet = new KeySet(name, publicKey, privateKey, signatureAlgorithm, signatureBlockTemplate);
}
public KeySet getKeySet() {
@ -271,11 +286,10 @@ public class ZipSigner
}
@SuppressWarnings("unchecked")
public void loadProvider( String providerClassName)
throws ClassNotFoundException, IllegalAccessException, InstantiationException
{
public void loadProvider(String providerClassName)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class providerClass = Class.forName(providerClassName);
Provider provider = (Provider)providerClass.newInstance();
Provider provider = (Provider) providerClass.newInstance();
Security.insertProviderAt(provider, 1);
}
@ -293,7 +307,7 @@ public class ZipSigner
/**
* Decrypt an encrypted PKCS 8 format private key.
*
* <p>
* Based on ghstark's post on Aug 6, 2006 at
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
*
@ -326,35 +340,39 @@ public class ZipSigner
}
}
/** Fetch the content at the specified URL and return it as a byte array. */
public byte[] readContentAsBytes( URL contentUrl) throws IOException
{
return readContentAsBytes( contentUrl.openStream());
/**
* Fetch the content at the specified URL and return it as a byte array.
*/
public byte[] readContentAsBytes(URL contentUrl) throws IOException {
return readContentAsBytes(contentUrl.openStream());
}
/** Fetch the content from the given stream and return it as a byte array. */
public byte[] readContentAsBytes( InputStream input) throws IOException
{
/**
* Fetch the content from the given stream and return it as a byte array.
*/
public byte[] readContentAsBytes(InputStream input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int numRead = input.read( buffer);
int numRead = input.read(buffer);
while (numRead != -1) {
baos.write( buffer, 0, numRead);
numRead = input.read( buffer);
baos.write(buffer, 0, numRead);
numRead = input.read(buffer);
}
byte[] bytes = baos.toByteArray();
return bytes;
}
/** Read a PKCS 8 format private key. */
/**
* Read a PKCS 8 format private key.
*/
public PrivateKey readPrivateKey(URL privateKeyUrl, String keyPassword)
throws IOException, GeneralSecurityException {
DataInputStream input = new DataInputStream( privateKeyUrl.openStream());
DataInputStream input = new DataInputStream(privateKeyUrl.openStream());
try {
byte[] bytes = readContentAsBytes( input);
byte[] bytes = readContentAsBytes(input);
KeySpec spec = decryptPrivateKey(bytes, keyPassword);
if (spec == null) {
@ -371,17 +389,16 @@ public class ZipSigner
}
}
/** Add the SHA1 of every file to the manifest, creating it if necessary. */
private Manifest addDigestsToManifest(Map<String,ZioEntry> entries)
throws IOException, GeneralSecurityException
{
/**
* Add the SHA1 of every file to the manifest, creating it if necessary.
*/
private Manifest addDigestsToManifest(Map<String, ZioEntry> entries)
throws IOException, GeneralSecurityException {
Manifest input = null;
ZioEntry manifestEntry = entries.get(JarFile.MANIFEST_NAME);
if (manifestEntry != null) {
InputStream is = manifestEntry.getInputStream();
input = new Manifest();
input.read(is);
is.close();
input.read(manifestEntry.getInputStream());
}
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
@ -402,21 +419,20 @@ public class ZipSigner
// map will be deterministic.
TreeMap<String, ZioEntry> byName = new TreeMap<String, ZioEntry>();
byName.putAll( entries);
byName.putAll(entries);
boolean debug = getLogger().isDebugEnabled();
if (debug) getLogger().debug("Manifest entries:");
for (ZioEntry entry: byName.values()) {
for (ZioEntry entry : byName.values()) {
if (canceled) break;
String name = entry.getName();
if (debug) getLogger().debug(name);
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||
!stripPattern.matcher(name).matches()))
{
!stripPattern.matcher(name).matches())) {
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_MANIFEST));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_MANIFEST));
InputStream data = entry.getInputStream();
while ((num = data.read(buffer)) > 0) {
md.update(buffer, 0, num);
@ -425,7 +441,7 @@ public class ZipSigner
Attributes attr = null;
if (input != null) {
java.util.jar.Attributes inAttr = input.getAttributes(name);
if (inAttr != null) attr = new Attributes( inAttr);
if (inAttr != null) attr = new Attributes(inAttr);
}
if (attr == null) attr = new Attributes();
attr.putValue("SHA1-Digest", Base64.encode(md.digest()));
@ -437,11 +453,13 @@ public class ZipSigner
}
/** Write the signature file to the given output stream. */
/**
* Write the signature file to the given output stream.
*/
private void generateSignatureFile(Manifest manifest, OutputStream out)
throws IOException, GeneralSecurityException {
out.write( ("Signature-Version: 1.0\r\n").getBytes());
out.write( ("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
out.write(("Signature-Version: 1.0\r\n").getBytes());
out.write(("Created-By: 1.0 (Android SignApk)\r\n").getBytes());
// BASE64Encoder base64 = new BASE64Encoder();
@ -454,32 +472,33 @@ public class ZipSigner
manifest.write(print);
print.flush();
out.write( ("SHA1-Digest-Manifest: "+ Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
out.write(("SHA1-Digest-Manifest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
if (canceled) break;
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_FILE));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_FILE));
// Digest of the manifest stanza for this entry.
String nameEntry = "Name: " + entry.getKey() + "\r\n";
print.print( nameEntry);
print.print(nameEntry);
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
out.write( nameEntry.getBytes());
out.write( ("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
out.write(nameEntry.getBytes());
out.write(("SHA1-Digest: " + Base64.encode(md.digest()) + "\r\n\r\n").getBytes());
}
}
/** Write a .RSA file with a digital signature. */
/**
* Write a .RSA file with a digital signature.
*/
@SuppressWarnings("unchecked")
private void writeSignatureBlock( KeySet keySet, byte[] signatureFileBytes, OutputStream out)
throws IOException, GeneralSecurityException
{
private void writeSignatureBlock(KeySet keySet, byte[] signatureFileBytes, OutputStream out)
throws IOException, GeneralSecurityException {
if (keySet.getSigBlockTemplate() != null) {
// Can't use default Signature on Android. Although it generates a signature that can be verified by jarsigner,
@ -490,35 +509,34 @@ public class ZipSigner
signature.update(signatureFileBytes);
byte[] signatureBytes = signature.sign();
out.write( keySet.getSigBlockTemplate());
out.write( signatureBytes);
out.write(keySet.getSigBlockTemplate());
out.write(signatureBytes);
if (getLogger().isDebugEnabled()) {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update( signatureFileBytes);
md.update(signatureFileBytes);
byte[] sfDigest = md.digest();
getLogger().debug( "Sig File SHA1: \n" + HexDumpEncoder.encode( sfDigest));
getLogger().debug("Sig File SHA1: \n" + HexDumpEncoder.encode(sfDigest));
getLogger().debug( "Signature: \n" + HexDumpEncoder.encode(signatureBytes));
getLogger().debug("Signature: \n" + HexDumpEncoder.encode(signatureBytes));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, keySet.getPublicKey());
byte[] tmpData = cipher.doFinal( signatureBytes);
getLogger().debug( "Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
byte[] tmpData = cipher.doFinal(signatureBytes);
getLogger().debug("Signature Decrypted: \n" + HexDumpEncoder.encode(tmpData));
}
}
else {
} else {
try {
byte[] sigBlock = null;
// Use reflection to call the optional generator.
Class generatorClass = Class.forName("kellinwood.security.zipsigner.optional.SignatureBlockGenerator");
Method generatorMethod = generatorClass.getMethod("generate", KeySet.class, (new byte[1]).getClass());
sigBlock = (byte[])generatorMethod.invoke(null, keySet, signatureFileBytes);
sigBlock = (byte[]) generatorMethod.invoke(null, keySet, signatureFileBytes);
out.write(sigBlock);
} catch (Exception x) {
throw new RuntimeException(x.getMessage(),x);
throw new RuntimeException(x.getMessage(), x);
}
}
}
@ -529,9 +547,8 @@ public class ZipSigner
* reduce variation in the output file and make incremental OTAs
* more efficient.
*/
private void copyFiles(Manifest manifest, Map<String,ZioEntry> input, ZipOutput output, long timestamp)
throws IOException
{
private void copyFiles(Manifest manifest, Map<String, ZioEntry> input, ZipOutput output, long timestamp)
throws IOException {
Map<String, Attributes> entries = manifest.getEntries();
List<String> names = new ArrayList<String>(entries.keySet());
Collections.sort(names);
@ -550,13 +567,12 @@ public class ZipSigner
/**
* Copy all the files from input to output.
*/
private void copyFiles(Map<String,ZioEntry> input, ZipOutput output)
throws IOException
{
private void copyFiles(Map<String, ZioEntry> input, ZipOutput output)
throws IOException {
int i = 1;
for (ZioEntry inEntry : input.values()) {
if (canceled) break;
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.COPYING_ZIP_ENTRY, i, input.size()));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.COPYING_ZIP_ENTRY, i, input.size()));
i += 1;
output.write(inEntry);
}
@ -565,7 +581,7 @@ public class ZipSigner
/**
* @deprecated - use the version that takes the passwords as char[]
*/
public void signZip( URL keystoreURL,
public void signZip(URL keystoreURL,
String keystoreType,
String keystorePw,
String certAlias,
@ -573,12 +589,11 @@ public class ZipSigner
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException
{
signZip( keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
IOException, GeneralSecurityException {
signZip(keystoreURL, keystoreType, keystorePw.toCharArray(), certAlias, certPw.toCharArray(), "SHA1withRSA", inputZipFilename, outputZipFilename);
}
public void signZip( URL keystoreURL,
public void signZip(URL keystoreURL,
String keystoreType,
char[] keystorePw,
String certAlias,
@ -587,8 +602,7 @@ public class ZipSigner
String inputZipFilename,
String outputZipFilename)
throws ClassNotFoundException, IllegalAccessException, InstantiationException,
IOException, GeneralSecurityException
{
IOException, GeneralSecurityException {
InputStream keystoreStream = null;
@ -600,64 +614,68 @@ public class ZipSigner
keystoreStream = keystoreURL.openStream();
keystore.load(keystoreStream, keystorePw);
Certificate cert = keystore.getCertificate(certAlias);
X509Certificate publicKey = (X509Certificate)cert;
X509Certificate publicKey = (X509Certificate) cert;
Key key = keystore.getKey(certAlias, certPw);
PrivateKey privateKey = (PrivateKey)key;
PrivateKey privateKey = (PrivateKey) key;
setKeys( "custom", publicKey, privateKey, signatureAlgorithm, null);
setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
signZip( inputZipFilename, outputZipFilename);
}
finally {
signZip(inputZipFilename, outputZipFilename);
} finally {
if (keystoreStream != null) keystoreStream.close();
}
}
/** Sign the input with the default test key and certificate.
/**
* Sign the input with the default test key and certificate.
* Save result to output file.
*/
public void signZip( Map<String,ZioEntry> zioEntries, String outputZipFilename)
throws IOException, GeneralSecurityException
{
public void signZip(Map<String, ZioEntry> zioEntries, String outputZipFilename)
throws IOException, GeneralSecurityException {
progressHelper.initProgress();
signZip( zioEntries, new FileOutputStream(outputZipFilename), outputZipFilename);
signZip(zioEntries, new FileOutputStream(outputZipFilename), outputZipFilename);
}
/** Sign the file using the given public key cert, private key,
/**
* Sign the file using the given public key cert, private key,
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
*/
public void signZip( String inputZipFilename, String outputZipFilename)
throws IOException, GeneralSecurityException
{
File inFile = new File( inputZipFilename).getCanonicalFile();
File outFile = new File( outputZipFilename).getCanonicalFile();
public void signZip(String inputZipFilename, String outputZipFilename)
throws IOException, GeneralSecurityException {
File inFile = new File(inputZipFilename).getCanonicalFile();
File outFile = new File(outputZipFilename).getCanonicalFile();
if (inFile.equals(outFile)) {
throw new IllegalArgumentException( resourceAdapter.getString(ResourceAdapter.Item.INPUT_SAME_AS_OUTPUT_ERROR));
throw new IllegalArgumentException(resourceAdapter.getString(ResourceAdapter.Item.INPUT_SAME_AS_OUTPUT_ERROR));
}
progressHelper.initProgress();
progressHelper.progress( ProgressEvent.PRORITY_IMPORTANT, resourceAdapter.getString(ResourceAdapter.Item.PARSING_CENTRAL_DIRECTORY));
progressHelper.progress(ProgressEvent.PRORITY_IMPORTANT, resourceAdapter.getString(ResourceAdapter.Item.PARSING_CENTRAL_DIRECTORY));
ZipInput input = ZipInput.read( inputZipFilename);
signZip( input.getEntries(), new FileOutputStream( outputZipFilename), outputZipFilename);
input.close();
ZipInput input = null;
OutputStream outStream = null;
try {
input = ZipInput.read(inputZipFilename);
outStream = new FileOutputStream(outputZipFilename);
signZip(input.getEntries(), outStream, outputZipFilename);
} finally {
if (input != null) input.close();
if (outStream != null) outStream.close();
}
}
/** Sign the
/**
* Sign the
* and signature block template. The signature block template
* parameter may be null, but if so
* android-sun-jarsign-support.jar must be in the classpath.
*/
public void signZip( Map<String,ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
throws IOException, GeneralSecurityException
{
public void signZip(Map<String, ZioEntry> zioEntries, OutputStream outputStream, String outputZipFilename)
throws IOException, GeneralSecurityException {
boolean debug = getLogger().isDebugEnabled();
progressHelper.initProgress();
@ -666,24 +684,23 @@ public class ZipSigner
throw new IllegalStateException("No keys configured for signing the file!");
// Auto-determine which keys to use
String keyName = this.autoDetectKey( keymode, zioEntries);
String keyName = this.autoDetectKey(keymode, zioEntries);
if (keyName == null)
throw new AutoKeyException( resourceAdapter.getString(ResourceAdapter.Item.AUTO_KEY_SELECTION_ERROR, new File( outputZipFilename).getName()));
throw new AutoKeyException(resourceAdapter.getString(ResourceAdapter.Item.AUTO_KEY_SELECTION_ERROR, new File(outputZipFilename).getName()));
autoKeyObservable.notifyObservers(keyName);
loadKeys( keyName);
loadKeys(keyName);
}
ZipOutput zipOutput = null;
try {
zipOutput = new ZipOutput( outputStream);
zipOutput = new ZipOutput(outputStream);
if (KEY_NONE.equals(keySet.getName())) {
progressHelper.setProgressTotalItems(zioEntries.size());
@ -694,13 +711,12 @@ public class ZipSigner
// Calculate total steps to complete for accurate progress percentages.
int progressTotalItems = 0;
for (ZioEntry entry: zioEntries.values()) {
for (ZioEntry entry : zioEntries.values()) {
String name = entry.getName();
if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
!name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
(stripPattern == null ||
!stripPattern.matcher(name).matches()))
{
!stripPattern.matcher(name).matches())) {
progressTotalItems += 3; // digest for manifest, digest in sig file, copy data
}
}
@ -715,7 +731,7 @@ public class ZipSigner
// progress(ProgressEvent.PRORITY_NORMAL, JarFile.MANIFEST_NAME);
Manifest manifest = addDigestsToManifest(zioEntries);
if (canceled) return;
ZioEntry ze = new ZioEntry( JarFile.MANIFEST_NAME);
ZioEntry ze = new ZioEntry(JarFile.MANIFEST_NAME);
ze.setTime(timestamp);
manifest.write(ze.getOutputStream());
zipOutput.write(ze);
@ -730,51 +746,46 @@ public class ZipSigner
if (canceled) return;
byte[] sfBytes = out.toByteArray();
if (debug) {
getLogger().debug( "Signature File: \n" + new String( sfBytes) + "\n" +
HexDumpEncoder.encode( sfBytes));
getLogger().debug("Signature File: \n" + new String(sfBytes) + "\n" +
HexDumpEncoder.encode(sfBytes));
}
ze.getOutputStream().write(sfBytes);
zipOutput.write(ze);
// CERT.RSA
progressHelper.progress( ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_BLOCK));
progressHelper.progress(ProgressEvent.PRORITY_NORMAL, resourceAdapter.getString(ResourceAdapter.Item.GENERATING_SIGNATURE_BLOCK));
ze = new ZioEntry(CERT_RSA_NAME);
ze.setTime(timestamp);
writeSignatureBlock(keySet, sfBytes, ze.getOutputStream());
zipOutput.write( ze);
zipOutput.write(ze);
if (canceled) return;
// Everything else
copyFiles(manifest, zioEntries, zipOutput, timestamp);
if (canceled) return;
}
finally {
zipOutput.close();
} finally {
if (zipOutput != null) zipOutput.close();
if (canceled) {
try {
if (outputZipFilename != null) new File( outputZipFilename).delete();
}
catch (Throwable t) {
getLogger().warning( t.getClass().getName() + ":" + t.getMessage());
if (outputZipFilename != null) new File(outputZipFilename).delete();
} catch (Throwable t) {
getLogger().warning(t.getClass().getName() + ":" + t.getMessage());
}
}
}
}
public void addProgressListener( ProgressListener l)
{
public void addProgressListener(ProgressListener l) {
progressHelper.addProgressListener(l);
}
public synchronized void removeProgressListener( ProgressListener l)
{
public synchronized void removeProgressListener(ProgressListener l) {
progressHelper.removeProgressListener(l);
}
public static class AutoKeyObservable extends Observable
{
public static class AutoKeyObservable extends Observable {
@Override
public void notifyObservers(Object arg) {
super.setChanged();

View File

@ -1,13 +1,17 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.KeySet;
import org.spongycastle.jce.X509Principal;
import org.spongycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
@ -17,7 +21,8 @@ import java.util.Date;
*/
public class CertCreator {
/** Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
/**
* Creates a new keystore and self-signed key. The key will have the same password as the key, and will be
* RSA 2048, with the cert signed using SHA1withRSA. The certificate will have a validity of
* 30 years).
*
@ -26,15 +31,14 @@ public class CertCreator {
* @param keyName - the new key will have this as its alias within the keystore
* @param distinguishedNameValues - contains Country, State, Locality,...,Common Name, etc.
*/
public static void createKeystoreAndKey( String storePath, char[] password,
String keyName, DistinguishedNameValues distinguishedNameValues)
{
public static void createKeystoreAndKey(String storePath, char[] password,
String keyName, DistinguishedNameValues distinguishedNameValues) {
createKeystoreAndKey(storePath, password, "RSA", 2048, keyName, password, "SHA1withRSA", 30,
distinguishedNameValues);
}
public static KeySet createKeystoreAndKey( String storePath, char[] storePass,
public static KeySet createKeystoreAndKey(String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
try {
@ -53,20 +57,20 @@ public class CertCreator {
if (sfile.exists()) {
throw new IOException("File already exists: " + storePath);
}
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
return keySet;
} catch (RuntimeException x) {
throw x;
} catch ( Exception x) {
throw new RuntimeException( x.getMessage(), x);
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
/** Create a new key and store it in an existing keystore.
*
/**
* Create a new key and store it in an existing keystore.
*/
public static KeySet createKey( String storePath, char[] storePass,
public static KeySet createKey(String storePath, char[] storePass,
String keyAlgorithm, int keySize, String keyName, char[] keyPass,
String certSignatureAlgorithm, int certValidityYears,
DistinguishedNameValues distinguishedNameValues) {
@ -81,20 +85,19 @@ public class CertCreator {
keyPass,
new java.security.cert.Certificate[]{keySet.getPublicKey()});
KeyStoreFileManager.writeKeyStore( privateKS, storePath, storePass);
KeyStoreFileManager.writeKeyStore(privateKS, storePath, storePass);
return keySet;
} catch (RuntimeException x) {
throw x;
} catch ( Exception x) {
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
public static KeySet createKey( String keyAlgorithm, int keySize, String keyName,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues)
{
public static KeySet createKey(String keyAlgorithm, int keySize, String keyName,
String certSignatureAlgorithm, int certValidityYears, DistinguishedNameValues distinguishedNameValues) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyAlgorithm);
keyPairGenerator.initialize(keySize);
@ -110,15 +113,15 @@ public class CertCreator {
serialNumber = BigInteger.valueOf(new SecureRandom().nextInt());
}
v3CertGen.setSerialNumber(serialNumber);
v3CertGen.setIssuerDN( principal);
v3CertGen.setIssuerDN(principal);
v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60L * 60L * 24L * 366L * (long)certValidityYears)));
v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60L * 60L * 24L * 366L * (long) certValidityYears)));
v3CertGen.setSubjectDN(principal);
v3CertGen.setPublicKey(KPair.getPublic());
v3CertGen.setSignatureAlgorithm(certSignatureAlgorithm);
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(),"BC");
X509Certificate PKCertificate = v3CertGen.generate(KPair.getPrivate(), "BC");
KeySet keySet = new KeySet();
keySet.setName(keyName);

View File

@ -0,0 +1,39 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.ZipSigner;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
/**
*/
public class CustomKeySigner {
/**
* KeyStore-type agnostic. This method will sign the zip file, automatically handling JKS or BKS keystores.
*/
public static void signZip(ZipSigner zipSigner,
String keystorePath,
char[] keystorePw,
String certAlias,
char[] certPw,
String signatureAlgorithm,
String inputZipFilename,
String outputZipFilename)
throws Exception {
zipSigner.issueLoadingCertAndKeysProgressEvent();
KeyStore keystore = KeyStoreFileManager.loadKeyStore(keystorePath, keystorePw);
Certificate cert = keystore.getCertificate(certAlias);
X509Certificate publicKey = (X509Certificate) cert;
Key key = keystore.getKey(certAlias, certPw);
PrivateKey privateKey = (PrivateKey) key;
zipSigner.setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
zipSigner.signZip(inputZipFilename, outputZipFilename);
}
}

View File

@ -0,0 +1,89 @@
package kellinwood.security.zipsigner.optional;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.jce.X509Principal;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
/**
* Helper class for dealing with the distinguished name RDNs.
*/
public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier, String> {
public DistinguishedNameValues() {
put(BCStyle.C, null);
put(BCStyle.ST, null);
put(BCStyle.L, null);
put(BCStyle.STREET, null);
put(BCStyle.O, null);
put(BCStyle.OU, null);
put(BCStyle.CN, null);
}
public String put(ASN1ObjectIdentifier oid, String value) {
if (value != null && value.equals("")) value = null;
if (containsKey(oid)) super.put(oid, value); // preserve original ordering
else {
super.put(oid, value);
// String cn = remove(BCStyle.CN); // CN will always be last.
// put(BCStyle.CN,cn);
}
return value;
}
public void setCountry(String country) {
put(BCStyle.C, country);
}
public void setState(String state) {
put(BCStyle.ST, state);
}
public void setLocality(String locality) {
put(BCStyle.L, locality);
}
public void setStreet(String street) {
put(BCStyle.STREET, street);
}
public void setOrganization(String organization) {
put(BCStyle.O, organization);
}
public void setOrganizationalUnit(String organizationalUnit) {
put(BCStyle.OU, organizationalUnit);
}
public void setCommonName(String commonName) {
put(BCStyle.CN, commonName);
}
@Override
public int size() {
int result = 0;
for (String value : values()) {
if (value != null) result += 1;
}
return result;
}
public X509Principal getPrincipal() {
Vector<ASN1ObjectIdentifier> oids = new Vector<ASN1ObjectIdentifier>();
Vector<String> values = new Vector<String>();
for (Map.Entry<ASN1ObjectIdentifier, String> entry : entrySet()) {
if (entry.getValue() != null && !entry.getValue().equals("")) {
oids.add(entry.getKey());
values.add(entry.getValue());
}
}
return new X509Principal(oids, values);
}
}

View File

@ -1,9 +1,10 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import kellinwood.security.zipsigner.Base64;
import org.spongycastle.util.encoders.HexTranslator;
import org.bouncycastle.util.encoders.HexTranslator;
import java.security.MessageDigest;
@ -15,34 +16,34 @@ public class Fingerprint {
static LoggerInterface logger = LoggerManager.getLogger(Fingerprint.class.getName());
static byte[] calcDigest( String algorithm, byte[] encodedCert) {
static byte[] calcDigest(String algorithm, byte[] encodedCert) {
byte[] result = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
messageDigest.update(encodedCert);
result = messageDigest.digest();
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return result;
}
public static String hexFingerprint( String algorithm, byte[] encodedCert) {
public static String hexFingerprint(String algorithm, byte[] encodedCert) {
try {
byte[] digest = calcDigest(algorithm,encodedCert);
byte[] digest = calcDigest(algorithm, encodedCert);
if (digest == null) return null;
HexTranslator hexTranslator = new HexTranslator();
byte[] hex = new byte[digest.length * 2];
hexTranslator.encode(digest, 0, digest.length, hex, 0);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hex.length; i += 2) {
builder.append((char)hex[i]);
builder.append((char)hex[i+1]);
builder.append((char) hex[i]);
builder.append((char) hex[i + 1]);
if (i != (hex.length - 2)) builder.append(':');
}
return builder.toString().toUpperCase();
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return null;
}
@ -55,14 +56,14 @@ public class Fingerprint {
//
// }
public static String base64Fingerprint( String algorithm, byte[] encodedCert) {
public static String base64Fingerprint(String algorithm, byte[] encodedCert) {
String result = null;
try {
byte[] digest = calcDigest(algorithm,encodedCert);
byte[] digest = calcDigest(algorithm, encodedCert);
if (digest == null) return result;
return Base64.encode(digest);
} catch (Exception x) {
logger.error(x.getMessage(),x);
logger.error(x.getMessage(), x);
}
return result;
}

View File

@ -23,15 +23,17 @@ power to enforce restrictions on reverse-engineering of their software,
and it is irresponsible for them to claim they can. */
package kellinwood.security.zipsigner.optional;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.Key;
@ -42,29 +44,24 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.spec.SecretKeySpec;
/**
* This is an implementation of Sun's proprietary key store
* algorithm, called "JKS" for "Java Key Store". This implementation was
* created entirely through reverse-engineering.
*
* <p>
* <p>The format of JKS files is, from the start of the file:
*
* <p>
* <ol>
* <li>Magic bytes. This is a four-byte integer, in big-endian byte
* order, equal to <code>0xFEEDFEED</code>.</li>
@ -82,7 +79,7 @@ import javax.crypto.spec.SecretKeySpec;
* href="http://java.sun.com/j2se/1.4.1/docs/api/java/io/DataOutput.html#writeUTF(java.lang.String)">DataOutput.writeUTF(String)</a>.</li>
* <li>An eight-byte integer, representing the entry's creation date,
* in milliseconds since the epoch.
*
* <p>
* <p>Then, if the entry is a private key entry:
* <ol>
* <li>The size of the encoded key as a four-byte int, then that
@ -94,7 +91,7 @@ import javax.crypto.spec.SecretKeySpec;
* certificates, encoded as described in the trusted certificates
* section.</li>
* </ol>
*
* <p>
* <p>Otherwise, the entry is a trusted certificate, which is encoded
* as the name of the encoding algorithm (e.g. X.509), encoded the same
* way as alias names. Then, a four-byte integer representing the size
@ -108,12 +105,12 @@ import javax.crypto.spec.SecretKeySpec;
* </ol>
* </li>
* </ol>
*
* <p>
* <p>(See <a href="genkey.java">this file</a> for some idea of how I
* was able to figure out these algorithms)</p>
*
* <p>
* <p>Decrypting the key works as follows:
*
* <p>
* <ol>
* <li>The key length is the length of the ciphertext minus 40. The
* encrypted key, <code>ekey</code>, is the middle bytes of the
@ -129,33 +126,34 @@ import javax.crypto.spec.SecretKeySpec;
* the last 20 bytes of the ciphertext, output <code>FAIL</code>. Otherwise,
* output <code>key</code>.</li>
* </ol>
*
* <p>
* <p>The signature is defined as <code>SHA-1(UTF-16BE(password) +
* US_ASCII("Mighty Aphrodite") + encoded_keystore)</code> (yup, Sun
* engineers are just that clever).
*
* <p>
* <p>(Above, SHA-1 denotes the secure hash algorithm, UTF-16BE the
* big-endian byte representation of a UTF-16 string, and US_ASCII the
* ASCII byte representation of the string.)
*
* <p>
* <p>The source code of this class should be available in the file <a
* href="http://metastatic.org/source/JKS.java">JKS.java</a>.
*
* @author Casey Marshall (rsdio@metastatic.org)
*
* <p>
* Changes by Ken Ellinwood:
* ** Fixed a NullPointerException in engineLoad(). This method must return gracefully if the keystore input stream is null.
* ** engineGetCertificateEntry() was updated to return the first cert in the chain for private key entries.
* ** Lowercase the alias names, otherwise keytool chokes on the file created by this code.
* ** Fixed the integrity check in engineLoad(), previously the exception was never thrown regardless of password value.
*/
public class JKS extends KeyStoreSpi
{
public class JKS extends KeyStoreSpi {
// Constants and fields.
// ------------------------------------------------------------------------
/** Ah, Sun. So goddamned clever with those magic bytes. */
/**
* Ah, Sun. So goddamned clever with those magic bytes.
*/
private static final int MAGIC = 0xFEEDFEED;
private static final int PRIVATE_KEY = 1;
@ -170,8 +168,7 @@ public class JKS extends KeyStoreSpi
// Constructor.
// ------------------------------------------------------------------------
public JKS()
{
public JKS() {
super();
aliases = new Vector();
trustedCerts = new HashMap();
@ -185,8 +182,7 @@ public class JKS extends KeyStoreSpi
// ------------------------------------------------------------------------
public Key engineGetKey(String alias, char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException
{
throws NoSuchAlgorithmException, UnrecoverableKeyException {
alias = alias.toLowerCase();
if (!privateKeys.containsKey(alias))
@ -194,32 +190,25 @@ public class JKS extends KeyStoreSpi
byte[] key = decryptKey((byte[]) privateKeys.get(alias),
charsToBytes(password));
Certificate[] chain = engineGetCertificateChain(alias);
if (chain.length > 0)
{
try
{
if (chain.length > 0) {
try {
// Private and public keys MUST have the same algorithm.
KeyFactory fact = KeyFactory.getInstance(
chain[0].getPublicKey().getAlgorithm());
return fact.generatePrivate(new PKCS8EncodedKeySpec(key));
}
catch (InvalidKeySpecException x)
{
} catch (InvalidKeySpecException x) {
throw new UnrecoverableKeyException(x.getMessage());
}
}
else
} else
return new SecretKeySpec(key, alias);
}
public Certificate[] engineGetCertificateChain(String alias)
{
public Certificate[] engineGetCertificateChain(String alias) {
alias = alias.toLowerCase();
return (Certificate[]) certChains.get(alias);
}
public Certificate engineGetCertificate(String alias)
{
public Certificate engineGetCertificate(String alias) {
alias = alias.toLowerCase();
if (engineIsKeyEntry(alias)) {
Certificate[] certChain = (Certificate[]) certChains.get(alias);
@ -228,8 +217,7 @@ public class JKS extends KeyStoreSpi
return (Certificate) trustedCerts.get(alias);
}
public Date engineGetCreationDate(String alias)
{
public Date engineGetCreationDate(String alias) {
alias = alias.toLowerCase();
return (Date) dates.get(alias);
}
@ -237,8 +225,7 @@ public class JKS extends KeyStoreSpi
// XXX implement writing methods.
public void engineSetKeyEntry(String alias, Key key, char[] passwd, Certificate[] certChain)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (trustedCerts.containsKey(alias))
throw new KeyStoreException("\"" + alias + " is a trusted certificate entry");
@ -247,25 +234,20 @@ public class JKS extends KeyStoreSpi
certChains.put(alias, certChain);
else
certChains.put(alias, new Certificate[0]);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineSetKeyEntry(String alias, byte[] encodedKey, Certificate[] certChain)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (trustedCerts.containsKey(alias))
throw new KeyStoreException("\"" + alias + "\" is a trusted certificate entry");
try
{
try {
new EncryptedPrivateKeyInfo(encodedKey);
}
catch (IOException ioe)
{
} catch (IOException ioe) {
throw new KeyStoreException("encoded key is not an EncryptedPrivateKeyInfo");
}
privateKeys.put(alias, encodedKey);
@ -273,67 +255,56 @@ public class JKS extends KeyStoreSpi
certChains.put(alias, certChain);
else
certChains.put(alias, new Certificate[0]);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineSetCertificateEntry(String alias, Certificate cert)
throws KeyStoreException
{
throws KeyStoreException {
alias = alias.toLowerCase();
if (privateKeys.containsKey(alias))
throw new KeyStoreException("\"" + alias + "\" is a private key entry");
if (cert == null)
throw new NullPointerException();
trustedCerts.put(alias, cert);
if (!aliases.contains(alias))
{
if (!aliases.contains(alias)) {
dates.put(alias, new Date());
aliases.add(alias);
}
}
public void engineDeleteEntry(String alias) throws KeyStoreException
{
public void engineDeleteEntry(String alias) throws KeyStoreException {
alias = alias.toLowerCase();
aliases.remove(alias);
}
public Enumeration engineAliases()
{
public Enumeration engineAliases() {
return aliases.elements();
}
public boolean engineContainsAlias(String alias)
{
public boolean engineContainsAlias(String alias) {
alias = alias.toLowerCase();
return aliases.contains(alias);
}
public int engineSize()
{
public int engineSize() {
return aliases.size();
}
public boolean engineIsKeyEntry(String alias)
{
public boolean engineIsKeyEntry(String alias) {
alias = alias.toLowerCase();
return privateKeys.containsKey(alias);
}
public boolean engineIsCertificateEntry(String alias)
{
public boolean engineIsCertificateEntry(String alias) {
alias = alias.toLowerCase();
return trustedCerts.containsKey(alias);
}
public String engineGetCertificateAlias(Certificate cert)
{
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); )
{
public String engineGetCertificateAlias(Certificate cert) {
for (Iterator keys = trustedCerts.keySet().iterator(); keys.hasNext(); ) {
String alias = (String) keys.next();
if (cert.equals(trustedCerts.get(alias)))
return alias;
@ -342,8 +313,7 @@ public class JKS extends KeyStoreSpi
}
public void engineStore(OutputStream out, char[] passwd)
throws IOException, NoSuchAlgorithmException, CertificateException
{
throws IOException, NoSuchAlgorithmException, CertificateException {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(charsToBytes(passwd));
md.update("Mighty Aphrodite".getBytes("UTF-8"));
@ -351,18 +321,14 @@ public class JKS extends KeyStoreSpi
dout.writeInt(MAGIC);
dout.writeInt(2);
dout.writeInt(aliases.size());
for (Enumeration e = aliases.elements(); e.hasMoreElements(); )
{
for (Enumeration e = aliases.elements(); e.hasMoreElements(); ) {
String alias = (String) e.nextElement();
if (trustedCerts.containsKey(alias))
{
if (trustedCerts.containsKey(alias)) {
dout.writeInt(TRUSTED_CERT);
dout.writeUTF(alias);
dout.writeLong(((Date) dates.get(alias)).getTime());
writeCert(dout, (Certificate) trustedCerts.get(alias));
}
else
{
} else {
dout.writeInt(PRIVATE_KEY);
dout.writeUTF(alias);
dout.writeLong(((Date) dates.get(alias)).getTime());
@ -380,8 +346,7 @@ public class JKS extends KeyStoreSpi
}
public void engineLoad(InputStream in, char[] passwd)
throws IOException, NoSuchAlgorithmException, CertificateException
{
throws IOException, NoSuchAlgorithmException, CertificateException {
MessageDigest md = MessageDigest.getInstance("SHA");
if (passwd != null) md.update(charsToBytes(passwd));
md.update("Mighty Aphrodite".getBytes("UTF-8")); // HAR HAR
@ -399,14 +364,12 @@ public class JKS extends KeyStoreSpi
aliases.ensureCapacity(n);
if (n < 0)
throw new LoadKeystoreException("Malformed key store");
for (int i = 0; i < n; i++)
{
for (int i = 0; i < n; i++) {
int type = din.readInt();
String alias = din.readUTF();
aliases.add(alias);
dates.put(alias, new Date(din.readLong()));
switch (type)
{
switch (type) {
case PRIVATE_KEY:
int len = din.readInt();
byte[] encoded = new byte[len];
@ -442,8 +405,7 @@ public class JKS extends KeyStoreSpi
// ------------------------------------------------------------------------
private static Certificate readCert(DataInputStream in)
throws IOException, CertificateException, NoSuchAlgorithmException
{
throws IOException, CertificateException, NoSuchAlgorithmException {
String type = in.readUTF();
int len = in.readInt();
byte[] encoded = new byte[len];
@ -453,8 +415,7 @@ public class JKS extends KeyStoreSpi
}
private static void writeCert(DataOutputStream dout, Certificate cert)
throws IOException, CertificateException
{
throws IOException, CertificateException {
dout.writeUTF(cert.getType());
byte[] b = cert.getEncoded();
dout.writeInt(b.length);
@ -462,29 +423,25 @@ public class JKS extends KeyStoreSpi
}
private static byte[] decryptKey(byte[] encryptedPKI, byte[] passwd)
throws UnrecoverableKeyException
{
try
{
throws UnrecoverableKeyException {
try {
EncryptedPrivateKeyInfo epki =
new EncryptedPrivateKeyInfo(encryptedPKI);
byte[] encr = epki.getEncryptedData();
byte[] keystream = new byte[20];
System.arraycopy(encr, 0, keystream, 0, 20);
byte[] check = new byte[20];
System.arraycopy(encr, encr.length-20, check, 0, 20);
System.arraycopy(encr, encr.length - 20, check, 0, 20);
byte[] key = new byte[encr.length - 40];
MessageDigest sha = MessageDigest.getInstance("SHA1");
int count = 0;
while (count < key.length)
{
while (count < key.length) {
sha.reset();
sha.update(passwd);
sha.update(keystream);
sha.digest(keystream, 0, keystream.length);
for (int i = 0; i < keystream.length && count < key.length; i++)
{
key[count] = (byte) (keystream[i] ^ encr[count+20]);
for (int i = 0; i < keystream.length && count < key.length; i++) {
key[count] = (byte) (keystream[i] ^ encr[count + 20]);
count++;
}
}
@ -494,18 +451,14 @@ public class JKS extends KeyStoreSpi
if (!MessageDigest.isEqual(check, sha.digest()))
throw new UnrecoverableKeyException("checksum mismatch");
return key;
}
catch (Exception x)
{
} catch (Exception x) {
throw new UnrecoverableKeyException(x.getMessage());
}
}
private static byte[] encryptKey(Key key, byte[] passwd)
throws KeyStoreException
{
try
{
throws KeyStoreException {
try {
MessageDigest sha = MessageDigest.getInstance("SHA1");
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
byte[] k = key.getEncoded();
@ -513,15 +466,13 @@ public class JKS extends KeyStoreSpi
byte[] keystream = rand.getSeed(20);
System.arraycopy(keystream, 0, encrypted, 0, 20);
int count = 0;
while (count < k.length)
{
while (count < k.length) {
sha.reset();
sha.update(passwd);
sha.update(keystream);
sha.digest(keystream, 0, keystream.length);
for (int i = 0; i < keystream.length && count < k.length; i++)
{
encrypted[count+20] = (byte) (keystream[i] ^ k[count]);
for (int i = 0; i < keystream.length && count < k.length; i++) {
encrypted[count + 20] = (byte) (keystream[i] ^ k[count]);
count++;
}
}
@ -533,18 +484,14 @@ public class JKS extends KeyStoreSpi
// encryption algorithm.
return new EncryptedPrivateKeyInfo("1.3.6.1.4.1.42.2.17.1.1",
encrypted).getEncoded();
}
catch (Exception x)
{
} catch (Exception x) {
throw new KeyStoreException(x.getMessage());
}
}
private static byte[] charsToBytes(char[] passwd)
{
private static byte[] charsToBytes(char[] passwd) {
byte[] buf = new byte[passwd.length * 2];
for (int i = 0, j = 0; i < passwd.length; i++)
{
for (int i = 0, j = 0; i < passwd.length; i++) {
buf[j++] = (byte) (passwd[i] >>> 8);
buf[j++] = (byte) passwd[i];
}

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
public class KeyNameConflictException extends Exception {

View File

@ -1,45 +1,55 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Key;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
/**
*/
public class KeyStoreFileManager {
static Provider provider = new BouncyCastleProvider();
public static Provider getProvider() { return provider; }
public static void setProvider(Provider provider) {
if (KeyStoreFileManager.provider != null) Security.removeProvider( KeyStoreFileManager.provider.getName());
KeyStoreFileManager.provider = provider;
Security.addProvider( provider);
public static Provider getProvider() {
return provider;
}
static LoggerInterface logger = LoggerManager.getLogger( KeyStoreFileManager.class.getName());
public static void setProvider(Provider provider) {
if (KeyStoreFileManager.provider != null) Security.removeProvider(KeyStoreFileManager.provider.getName());
KeyStoreFileManager.provider = provider;
Security.addProvider(provider);
}
static LoggerInterface logger = LoggerManager.getLogger(KeyStoreFileManager.class.getName());
static {
// Add the spongycastle version of the BC provider so that the implementation classes returned
// from the keystore are all from the spongycastle libs.
// Add the bouncycastle version of the BC provider so that the implementation classes returned
// from the keystore are all from the bouncycastle libs.
Security.addProvider(getProvider());
}
public static KeyStore loadKeyStore( String keystorePath, String encodedPassword)
throws Exception{
public static KeyStore loadKeyStore(String keystorePath, String encodedPassword)
throws Exception {
char password[] = null;
try {
if (encodedPassword != null) {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
}
return loadKeyStore(keystorePath, password);
} finally {
@ -47,27 +57,24 @@ public class KeyStoreFileManager {
}
}
public static KeyStore createKeyStore( String keystorePath, char[] password)
throws Exception
{
public static KeyStore createKeyStore(String keystorePath, char[] password)
throws Exception {
KeyStore ks = null;
if (keystorePath.toLowerCase().endsWith(".bks")) {
ks = KeyStore.getInstance("bks", new BouncyCastleProvider());
}
else ks = new JksKeyStore();
} else ks = new JksKeyStore();
ks.load(null, password);
return ks;
}
public static KeyStore loadKeyStore( String keystorePath, char[] password)
throws Exception
{
public static KeyStore loadKeyStore(String keystorePath, char[] password)
throws Exception {
KeyStore ks = null;
try {
ks = new JksKeyStore();
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
FileInputStream fis = new FileInputStream(keystorePath);
ks.load(fis, password);
fis.close();
return ks;
} catch (LoadKeystoreException x) {
@ -78,8 +85,8 @@ public class KeyStoreFileManager {
// logger.warning( x.getMessage(), x);
try {
ks = KeyStore.getInstance("bks", getProvider());
FileInputStream fis = new FileInputStream( keystorePath);
ks.load( fis, password);
FileInputStream fis = new FileInputStream(keystorePath);
ks.load(fis, password);
fis.close();
return ks;
} catch (Exception e) {
@ -88,29 +95,27 @@ public class KeyStoreFileManager {
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, String encodedPassword)
throws Exception
{
public static void writeKeyStore(KeyStore ks, String keystorePath, String encodedPassword)
throws Exception {
char password[] = null;
try {
password = PasswordObfuscator.getInstance().decodeKeystorePassword( keystorePath, encodedPassword);
writeKeyStore( ks, keystorePath, password);
password = PasswordObfuscator.getInstance().decodeKeystorePassword(keystorePath, encodedPassword);
writeKeyStore(ks, keystorePath, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
}
public static void writeKeyStore( KeyStore ks, String keystorePath, char[] password)
throws Exception
{
public static void writeKeyStore(KeyStore ks, String keystorePath, char[] password)
throws Exception {
File keystoreFile = new File( keystorePath);
File keystoreFile = new File(keystorePath);
try {
if (keystoreFile.exists()) {
// I've had some trouble saving new verisons of the keystore file in which the file becomes empty/corrupt.
// Saving the new version to a new file and creating a backup of the old version.
File tmpFile = File.createTempFile( keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream( tmpFile);
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream(tmpFile);
ks.store(fos, password);
fos.flush();
fos.close();
@ -125,18 +130,19 @@ public class KeyStoreFileManager {
*/
renameTo(tmpFile, keystoreFile);
} else {
FileOutputStream fos = new FileOutputStream( keystorePath);
FileOutputStream fos = new FileOutputStream(keystorePath);
ks.store(fos, password);
fos.close();
}
} catch (Exception x) {
try {
File logfile = File.createTempFile("zipsigner-error", ".log", keystoreFile.getParentFile());
PrintWriter pw = new PrintWriter(new FileWriter( logfile));
x.printStackTrace( pw);
PrintWriter pw = new PrintWriter(new FileWriter(logfile));
x.printStackTrace(pw);
pw.flush();
pw.close();
} catch (Exception y) {}
} catch (Exception y) {
}
throw x;
}
}
@ -159,10 +165,16 @@ public class KeyStoreFileManager {
count += n;
}
} finally {
try { output.close(); } catch (IOException x) {} // Ignore
try {
output.close();
} catch (IOException x) {
} // Ignore
}
} finally {
try { input.close(); } catch (IOException x) {}
try {
input.close();
} catch (IOException x) {
}
}
if (srcFile.length() != destFile.length()) {
@ -176,23 +188,20 @@ public class KeyStoreFileManager {
public static void renameTo(File fromFile, File toFile)
throws IOException
{
throws IOException {
copyFile(fromFile, toFile, true);
if (!fromFile.delete()) throw new IOException("Failed to delete " + fromFile);
}
public static void deleteKey(String storePath, String storePass, String keyName)
throws Exception
{
KeyStore ks = loadKeyStore( storePath, storePass);
ks.deleteEntry( keyName);
throws Exception {
KeyStore ks = loadKeyStore(storePath, storePass);
ks.deleteEntry(keyName);
writeKeyStore(ks, storePath, storePass);
}
public static String renameKey( String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
throws Exception
{
public static String renameKey(String keystorePath, String storePass, String oldKeyName, String newKeyName, String keyPass)
throws Exception {
char[] keyPw = null;
try {
@ -201,59 +210,53 @@ public class KeyStoreFileManager {
if (ks.containsAlias(newKeyName)) throw new KeyNameConflictException();
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, oldKeyName, keyPass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, oldKeyName, keyPass);
Key key = ks.getKey(oldKeyName, keyPw);
Certificate cert = ks.getCertificate( oldKeyName);
Certificate cert = ks.getCertificate(oldKeyName);
ks.setKeyEntry(newKeyName, key, keyPw, new Certificate[] { cert});
ks.deleteEntry( oldKeyName);
ks.setKeyEntry(newKeyName, key, keyPw, new Certificate[]{cert});
ks.deleteEntry(oldKeyName);
writeKeyStore(ks, keystorePath, storePass);
return newKeyName;
}
finally {
} finally {
PasswordObfuscator.flush(keyPw);
}
}
public static KeyStore.Entry getKeyEntry( String keystorePath, String storePass, String keyName, String keyPass)
throws Exception
{
public static KeyStore.Entry getKeyEntry(String keystorePath, String storePass, String keyName, String keyPass)
throws Exception {
char[] keyPw = null;
KeyStore.PasswordProtection passwordProtection = null;
try {
KeyStore ks = loadKeyStore(keystorePath, storePass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword( keystorePath, keyName, keyPass);
keyPw = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, keyPass);
passwordProtection = new KeyStore.PasswordProtection(keyPw);
return ks.getEntry( keyName, passwordProtection);
}
finally {
return ks.getEntry(keyName, passwordProtection);
} finally {
if (keyPw != null) PasswordObfuscator.flush(keyPw);
if (passwordProtection != null) passwordProtection.destroy();
}
}
public static boolean containsKey( String keystorePath, String storePass, String keyName)
throws Exception
{
public static boolean containsKey(String keystorePath, String storePass, String keyName)
throws Exception {
KeyStore ks = loadKeyStore(keystorePath, storePass);
return ks.containsAlias( keyName);
return ks.containsAlias(keyName);
}
/**
*
* @param keystorePath
* @param encodedPassword
* @throws Exception if the password is invalid
*/
public static void validateKeystorePassword( String keystorePath, String encodedPassword)
throws Exception
{
public static void validateKeystorePassword(String keystorePath, String encodedPassword)
throws Exception {
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, encodedPassword);
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, encodedPassword);
} finally {
if (password != null) PasswordObfuscator.flush(password);
}
@ -261,19 +264,17 @@ public class KeyStoreFileManager {
}
/**
*
* @param keystorePath
* @param keyName
* @param encodedPassword
* @throws java.security.UnrecoverableKeyException if the password is invalid
*/
public static void validateKeyPassword( String keystorePath, String keyName, String encodedPassword)
throws Exception
{
public static void validateKeyPassword(String keystorePath, String keyName, String encodedPassword)
throws Exception {
char[] password = null;
try {
KeyStore ks = KeyStoreFileManager.loadKeyStore( keystorePath, (char[])null);
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath,keyName, encodedPassword);
KeyStore ks = KeyStoreFileManager.loadKeyStore(keystorePath, (char[]) null);
password = PasswordObfuscator.getInstance().decodeAliasPassword(keystorePath, keyName, encodedPassword);
ks.getKey(keyName, password);
} finally {
if (password != null) PasswordObfuscator.flush(password);

View File

@ -1,3 +1,4 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.logging.LoggerInterface;
@ -6,7 +7,12 @@ import kellinwood.security.zipsigner.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class PasswordObfuscator {
@ -18,7 +24,7 @@ public class PasswordObfuscator {
SecretKeySpec skeySpec;
private PasswordObfuscator() {
logger = LoggerManager.getLogger( PasswordObfuscator.class.getName());
logger = LoggerManager.getLogger(PasswordObfuscator.class.getName());
skeySpec = new SecretKeySpec(x.getBytes(), "AES");
}
@ -27,39 +33,39 @@ public class PasswordObfuscator {
return instance;
}
public String encodeKeystorePassword( String keystorePath, String password) {
return encode( keystorePath, password);
public String encodeKeystorePassword(String keystorePath, String password) {
return encode(keystorePath, password);
}
public String encodeKeystorePassword( String keystorePath, char[] password) {
return encode( keystorePath, password);
public String encodeKeystorePassword(String keystorePath, char[] password) {
return encode(keystorePath, password);
}
public String encodeAliasPassword( String keystorePath, String aliasName, String password) {
return encode( keystorePath+aliasName, password);
public String encodeAliasPassword(String keystorePath, String aliasName, String password) {
return encode(keystorePath + aliasName, password);
}
public String encodeAliasPassword( String keystorePath, String aliasName, char[] password) {
return encode( keystorePath+aliasName, password);
public String encodeAliasPassword(String keystorePath, String aliasName, char[] password) {
return encode(keystorePath + aliasName, password);
}
public char[] decodeKeystorePassword( String keystorePath, String password) {
return decode(keystorePath,password);
public char[] decodeKeystorePassword(String keystorePath, String password) {
return decode(keystorePath, password);
}
public char[] decodeAliasPassword( String keystorePath, String aliasName, String password) {
return decode(keystorePath+aliasName,password);
public char[] decodeAliasPassword(String keystorePath, String aliasName, String password) {
return decode(keystorePath + aliasName, password);
}
public String encode( String junk, String password) {
public String encode(String junk, String password) {
if (password == null) return null;
char[] c = password.toCharArray();
String result = encode( junk, c);
String result = encode(junk, c);
flush(c);
return result;
}
public String encode( String junk, char[] password) {
public String encode(String junk, char[] password) {
if (password == null) return null;
try {
// Instantiate the cipher
@ -70,33 +76,33 @@ public class PasswordObfuscator {
w.write(junk);
w.write(password);
w.flush();
byte[] encoded = cipher.doFinal( baos.toByteArray());
return Base64.encode( encoded);
byte[] encoded = cipher.doFinal(baos.toByteArray());
return Base64.encode(encoded);
} catch (Exception x) {
logger.error("Failed to obfuscate password", x);
}
return null;
}
public char[] decode( String junk, String password) {
public char[] decode(String junk, String password) {
if (password == null) return null;
try {
// Instantiate the cipher
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec skeySpec = new SecretKeySpec(x.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] bytes = cipher.doFinal( Base64.decode(password.getBytes()));
BufferedReader r = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( bytes)));
byte[] bytes = cipher.doFinal(Base64.decode(password.getBytes()));
BufferedReader r = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
char[] cb = new char[128];
int length = 0;
int numRead;
while ((numRead = r.read(cb, length, 128-length)) != -1) {
while ((numRead = r.read(cb, length, 128 - length)) != -1) {
length += numRead;
}
if (length <= junk.length()) return null;
char[] result = new char[ length - junk.length()];
char[] result = new char[length - junk.length()];
int j = 0;
for (int i = junk.length(); i < length; i++) {
result[j] = cb[i];
@ -111,14 +117,14 @@ public class PasswordObfuscator {
return null;
}
public static void flush( char[] charArray) {
public static void flush(char[] charArray) {
if (charArray == null) return;
for (int i = 0; i < charArray.length; i++) {
charArray[i] = '\0';
}
}
public static void flush( byte[] charArray) {
public static void flush(byte[] charArray) {
if (charArray == null) return;
for (int i = 0; i < charArray.length; i++) {
charArray[i] = 0;

View File

@ -1,14 +1,19 @@
package kellinwood.security.zipsigner.optional;
import kellinwood.security.zipsigner.KeySet;
import org.spongycastle.cert.jcajce.JcaCertStore;
import org.spongycastle.cms.*;
import org.spongycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.DigestCalculatorProvider;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.spongycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.spongycastle.util.Store;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import java.util.ArrayList;
import java.util.List;
@ -39,11 +44,11 @@ public class SignatureBlockGenerator {
JcaDigestCalculatorProviderBuilder jcaDigestCalculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().setProvider("SC");
DigestCalculatorProvider digestCalculatorProvider = jcaDigestCalculatorProviderBuilder.build();
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder( digestCalculatorProvider);
JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider);
jcaSignerInfoGeneratorBuilder.setDirectSignature(true);
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keySet.getPublicKey());
gen.addSignerInfoGenerator( signerInfoGenerator);
gen.addSignerInfoGenerator(signerInfoGenerator);
gen.addCertificates(certs);

View File

@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.io.IOException;
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
public class CentralEnd
{
import java.io.IOException;
public class CentralEnd {
public int signature = 0x06054b50; // end of central dir signature 4 bytes
public short numberThisDisk = 0; // number of this disk 2 bytes
public short centralStartDisk = 0; // number of the disk with the start of the central directory 2 bytes
@ -34,73 +34,70 @@ public class CentralEnd
private static LoggerInterface log;
public static CentralEnd read(ZipInput input) throws IOException
{
public static CentralEnd read(ZipInput input) throws IOException {
int signature = input.readInt();
if (signature != 0x06054b50) {
// back up to the signature
input.seek( input.getFilePointer() - 4);
input.seek(input.getFilePointer() - 4);
return null;
}
CentralEnd entry = new CentralEnd();
entry.doRead( input);
entry.doRead(input);
return entry;
}
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( CentralEnd.class.getName());
if (log == null) log = LoggerManager.getLogger(CentralEnd.class.getName());
return log;
}
private void doRead( ZipInput input) throws IOException
{
private void doRead(ZipInput input) throws IOException {
boolean debug = getLogger().isDebugEnabled();
numberThisDisk = input.readShort();
if (debug) log.debug( String.format("This disk number: 0x%04x", numberThisDisk));
if (debug) log.debug(String.format("This disk number: 0x%04x", numberThisDisk));
centralStartDisk = input.readShort();
if (debug) log.debug( String.format("Central dir start disk number: 0x%04x", centralStartDisk));
if (debug) log.debug(String.format("Central dir start disk number: 0x%04x", centralStartDisk));
numCentralEntries = input.readShort();
if (debug) log.debug( String.format("Central entries on this disk: 0x%04x", numCentralEntries));
if (debug) log.debug(String.format("Central entries on this disk: 0x%04x", numCentralEntries));
totalCentralEntries = input.readShort();
if (debug) log.debug( String.format("Total number of central entries: 0x%04x", totalCentralEntries));
if (debug) log.debug(String.format("Total number of central entries: 0x%04x", totalCentralEntries));
centralDirectorySize = input.readInt();
if (debug) log.debug( String.format("Central directory size: 0x%08x", centralDirectorySize));
if (debug) log.debug(String.format("Central directory size: 0x%08x", centralDirectorySize));
centralStartOffset = input.readInt();
if (debug) log.debug( String.format("Central directory offset: 0x%08x", centralStartOffset));
if (debug) log.debug(String.format("Central directory offset: 0x%08x", centralStartOffset));
short zipFileCommentLen = input.readShort();
fileComment = input.readString(zipFileCommentLen);
if (debug) log.debug( ".ZIP file comment: " + fileComment);
if (debug) log.debug(".ZIP file comment: " + fileComment);
}
public void write( ZipOutput output) throws IOException
{
public void write(ZipOutput output) throws IOException {
boolean debug = getLogger().isDebugEnabled();
output.writeInt( signature);
output.writeShort( numberThisDisk);
output.writeShort( centralStartDisk);
output.writeShort( numCentralEntries);
output.writeShort( totalCentralEntries);
output.writeInt( centralDirectorySize );
output.writeInt( centralStartOffset );
output.writeShort( (short)fileComment.length());
output.writeString( fileComment);
output.writeInt(signature);
output.writeShort(numberThisDisk);
output.writeShort(centralStartDisk);
output.writeShort(numCentralEntries);
output.writeShort(totalCentralEntries);
output.writeInt(centralDirectorySize);
output.writeInt(centralStartOffset);
output.writeShort((short) fileComment.length());
output.writeString(fileComment);
}

View File

@ -13,13 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.Date;
@ -27,9 +31,6 @@ import java.util.zip.CRC32;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
public class ZioEntry implements Cloneable {
private ZipInput zipInput;
@ -62,36 +63,35 @@ public class ZioEntry implements Cloneable {
private static LoggerInterface log;
public ZioEntry( ZipInput input) {
public ZioEntry(ZipInput input) {
zipInput = input;
}
public static LoggerInterface getLogger() {
if (log == null) log = LoggerManager.getLogger( ZioEntry.class.getName());
if (log == null) log = LoggerManager.getLogger(ZioEntry.class.getName());
return log;
}
public ZioEntry( String name) {
public ZioEntry(String name) {
filename = name;
fileComment = "";
compression = 8;
extraData = new byte[0];
setTime( System.currentTimeMillis());
setTime(System.currentTimeMillis());
}
public ZioEntry( String name, String sourceDataFile)
throws IOException
{
zipInput = new ZipInput( sourceDataFile);
public ZioEntry(String name, String sourceDataFile)
throws IOException {
zipInput = new ZipInput(sourceDataFile);
filename = name;
fileComment = "";
this.compression = 0;
this.size = (int)zipInput.getFileLength();
this.size = (int) zipInput.getFileLength();
this.compressedSize = this.size;
if (getLogger().isDebugEnabled())
getLogger().debug(String.format("Computing CRC for %s, size=%d",sourceDataFile,size));
getLogger().debug(String.format("Computing CRC for %s, size=%d", sourceDataFile, size));
// compute CRC
CRC32 crc = new CRC32();
@ -100,27 +100,25 @@ public class ZioEntry implements Cloneable {
int numRead = 0;
while (numRead != size) {
int count = zipInput.read( buffer, 0, Math.min( buffer.length, (this.size - numRead)));
int count = zipInput.read(buffer, 0, Math.min(buffer.length, (this.size - numRead)));
if (count > 0) {
crc.update( buffer, 0, count);
crc.update(buffer, 0, count);
numRead += count;
}
}
this.crc32 = (int)crc.getValue();
this.crc32 = (int) crc.getValue();
zipInput.seek(0);
this.dataPosition = 0;
extraData = new byte[0];
setTime( new File(sourceDataFile).lastModified());
setTime(new File(sourceDataFile).lastModified());
}
public ZioEntry( String name, String sourceDataFile, short compression, int crc32, int compressedSize, int size)
throws IOException
{
zipInput = new ZipInput( sourceDataFile);
public ZioEntry(String name, String sourceDataFile, short compression, int crc32, int compressedSize, int size)
throws IOException {
zipInput = new ZipInput(sourceDataFile);
filename = name;
fileComment = "";
this.compression = compression;
@ -129,39 +127,35 @@ public class ZioEntry implements Cloneable {
this.size = size;
this.dataPosition = 0;
extraData = new byte[0];
setTime( new File(sourceDataFile).lastModified());
setTime(new File(sourceDataFile).lastModified());
}
// Return a copy with a new name
public ZioEntry getClonedEntry( String newName)
{
public ZioEntry getClonedEntry(String newName) {
ZioEntry clone;
try {
clone = (ZioEntry)this.clone();
}
catch (CloneNotSupportedException e)
{
clone = (ZioEntry) this.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("clone() failed!");
}
clone.setName(newName);
return clone;
}
public void readLocalHeader() throws IOException
{
public void readLocalHeader() throws IOException {
ZipInput input = zipInput;
int tmp;
boolean debug = getLogger().isDebugEnabled();
input.seek( localHeaderOffset);
input.seek(localHeaderOffset);
if (debug) getLogger().debug( String.format("FILE POSITION: 0x%08x", input.getFilePointer()));
if (debug) getLogger().debug(String.format("FILE POSITION: 0x%08x", input.getFilePointer()));
// 0 4 Local file header signature = 0x04034b50
int signature = input.readInt();
if (signature != 0x04034b50) {
throw new IllegalStateException( String.format("Local header not found at pos=0x%08x, file=%s", input.getFilePointer(), filename));
throw new IllegalStateException(String.format("Local header not found at pos=0x%08x, file=%s", input.getFilePointer(), filename));
}
// This method is usually called just before the data read, so
@ -174,36 +168,44 @@ public class ZioEntry implements Cloneable {
short tmpShort;
// 4 2 Version needed to extract (minimum)
/* versionRequired */ tmpShort = input.readShort();
/* versionRequired */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Version required: 0x%04x", tmpShort /*versionRequired*/));
// 6 2 General purpose bit flag
/* generalPurposeBits */ tmpShort = input.readShort();
/* generalPurposeBits */
tmpShort = input.readShort();
if (debug) log.debug(String.format("General purpose bits: 0x%04x", tmpShort /* generalPurposeBits */));
// 8 2 Compression method
/* compression */ tmpShort = input.readShort();
/* compression */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Compression: 0x%04x", tmpShort /* compression */));
// 10 2 File last modification time
/* modificationTime */ tmpShort = input.readShort();
/* modificationTime */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Modification time: 0x%04x", tmpShort /* modificationTime */));
// 12 2 File last modification date
/* modificationDate */ tmpShort = input.readShort();
/* modificationDate */
tmpShort = input.readShort();
if (debug) log.debug(String.format("Modification date: 0x%04x", tmpShort /* modificationDate */));
// 14 4 CRC-32
/* crc32 */ tmpInt = input.readInt();
/* crc32 */
tmpInt = input.readInt();
if (debug) log.debug(String.format("CRC-32: 0x%04x", tmpInt /*crc32*/));
// 18 4 Compressed size
/* compressedSize*/ tmpInt = input.readInt();
/* compressedSize*/
tmpInt = input.readInt();
if (debug) log.debug(String.format("Compressed size: 0x%04x", tmpInt /*compressedSize*/));
// 22 4 Uncompressed size
/* size */ tmpInt = input.readInt();
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/ ));
/* size */
tmpInt = input.readInt();
if (debug) log.debug(String.format("Size: 0x%04x", tmpInt /*size*/));
// 26 2 File name length (n)
short fileNameLen = input.readShort();
@ -218,46 +220,45 @@ public class ZioEntry implements Cloneable {
if (debug) log.debug("Filename: " + filename);
// Extra data
byte[] extra = input.readBytes( extraLen);
byte[] extra = input.readBytes(extraLen);
// Record the file position of this entry's data.
dataPosition = input.getFilePointer();
if (debug) log.debug(String.format("Data position: 0x%08x",dataPosition));
if (debug) log.debug(String.format("Data position: 0x%08x", dataPosition));
}
public void writeLocalEntry( ZipOutput output) throws IOException
{
public void writeLocalEntry(ZipOutput output) throws IOException {
if (data == null && dataPosition < 0 && zipInput != null) {
readLocalHeader();
}
localHeaderOffset = (int)output.getFilePointer();
localHeaderOffset = (int) output.getFilePointer();
boolean debug = getLogger().isDebugEnabled();
if (debug) {
getLogger().debug( String.format("Writing local header at 0x%08x - %s", localHeaderOffset, filename));
getLogger().debug(String.format("Writing local header at 0x%08x - %s", localHeaderOffset, filename));
}
if (entryOut != null) {
entryOut.close();
size = entryOut.getSize();
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
compressedSize = data.length;
crc32 = entryOut.getCRC();
}
output.writeInt( 0x04034b50);
output.writeShort( versionRequired);
output.writeShort( generalPurposeBits);
output.writeShort( compression);
output.writeShort( modificationTime);
output.writeShort( modificationDate);
output.writeInt( crc32);
output.writeInt( compressedSize);
output.writeInt( size);
output.writeShort( (short)filename.length());
output.writeInt(0x04034b50);
output.writeShort(versionRequired);
output.writeShort(generalPurposeBits);
output.writeShort(compression);
output.writeShort(modificationTime);
output.writeShort(modificationDate);
output.writeInt(crc32);
output.writeInt(compressedSize);
output.writeInt(size);
output.writeShort((short) filename.length());
numAlignBytes = 0;
@ -269,73 +270,70 @@ public class ZioEntry implements Cloneable {
filename.length() + // plus filename
extraData.length; // plus extra data
short dataPosMod4 = (short)(dataPos % 4);
short dataPosMod4 = (short) (dataPos % 4);
if (dataPosMod4 > 0) {
numAlignBytes = (short)(4 - dataPosMod4);
numAlignBytes = (short) (4 - dataPosMod4);
}
}
// 28 2 Extra field length (m)
output.writeShort( (short)(extraData.length + numAlignBytes));
output.writeShort((short) (extraData.length + numAlignBytes));
// 30 n File name
output.writeString( filename);
output.writeString(filename);
// Extra data
output.writeBytes( extraData);
output.writeBytes(extraData);
// Zipalign bytes
if (numAlignBytes > 0) {
output.writeBytes( alignBytes, 0, numAlignBytes);
output.writeBytes(alignBytes, 0, numAlignBytes);
}
if (debug) getLogger().debug(String.format("Data position 0x%08x", output.getFilePointer()));
if (data != null) {
output.writeBytes( data);
output.writeBytes(data);
if (debug) getLogger().debug(String.format("Wrote %d bytes", data.length));
}
else {
} else {
if (debug) getLogger().debug(String.format("Seeking to position 0x%08x", dataPosition));
zipInput.seek( dataPosition);
zipInput.seek(dataPosition);
int bufferSize = Math.min( compressedSize, 8096);
int bufferSize = Math.min(compressedSize, 8096);
byte[] buffer = new byte[bufferSize];
long totalCount = 0;
while (totalCount != compressedSize) {
int numRead = zipInput.in.read( buffer, 0, (int)Math.min( compressedSize - totalCount, bufferSize));
int numRead = zipInput.in.read(buffer, 0, (int) Math.min(compressedSize - totalCount, bufferSize));
if (numRead > 0) {
output.writeBytes(buffer, 0, numRead);
if (debug) getLogger().debug(String.format("Wrote %d bytes", numRead));
totalCount += numRead;
}
else throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount));
} else
throw new IllegalStateException(String.format("EOF reached while copying %s with %d bytes left to go", filename, compressedSize - totalCount));
}
}
}
public static ZioEntry read(ZipInput input) throws IOException
{
public static ZioEntry read(ZipInput input) throws IOException {
// 0 4 Central directory header signature = 0x02014b50
int signature = input.readInt();
if (signature != 0x02014b50) {
// back up to the signature
input.seek( input.getFilePointer() - 4);
input.seek(input.getFilePointer() - 4);
return null;
}
ZioEntry entry = new ZioEntry( input);
ZioEntry entry = new ZioEntry(input);
entry.doRead( input);
entry.doRead(input);
return entry;
}
private void doRead( ZipInput input) throws IOException
{
private void doRead(ZipInput input) throws IOException {
boolean debug = getLogger().isDebugEnabled();
@ -352,7 +350,7 @@ public class ZioEntry implements Cloneable {
if (debug) log.debug(String.format("General purpose bits: 0x%04x", generalPurposeBits));
// Bits 1, 2, 3, and 11 are allowed to be set (first bit is bit zero). Any others are a problem.
if ((generalPurposeBits & 0xF7F1) != 0x0000) {
throw new IllegalStateException("Can't handle general purpose bits == "+String.format("0x%04x",generalPurposeBits));
throw new IllegalStateException("Can't handle general purpose bits == " + String.format("0x%04x", generalPurposeBits));
}
// 8 2 Compression method
@ -406,12 +404,12 @@ public class ZioEntry implements Cloneable {
filename = input.readString(fileNameLen);
if (debug) log.debug("Filename: " + filename);
extraData = input.readBytes( extraLen);
extraData = input.readBytes(extraLen);
fileComment = input.readString( fileCommentLen);
fileComment = input.readString(fileCommentLen);
if (debug) log.debug("File comment: " + fileComment);
generalPurposeBits = (short)(generalPurposeBits & 0x0800); // Don't write a data descriptor, preserve UTF-8 encoded filename bit
generalPurposeBits = (short) (generalPurposeBits & 0x0800); // Don't write a data descriptor, preserve UTF-8 encoded filename bit
// Don't write zero-length entries with compression.
if (size == 0) {
@ -422,9 +420,10 @@ public class ZioEntry implements Cloneable {
}
/** Returns the entry's data. */
public byte[] getData() throws IOException
{
/**
* Returns the entry's data.
*/
public byte[] getData() throws IOException {
if (data != null) return data;
byte[] tmpdata = new byte[size];
@ -433,8 +432,9 @@ public class ZioEntry implements Cloneable {
int count = 0;
while (count != size) {
int numRead = din.read( tmpdata, count, size-count);
if (numRead < 0) throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count));
int numRead = din.read(tmpdata, count, size - count);
if (numRead < 0)
throw new IllegalStateException(String.format("Read failed, expecting %d bytes, got %d instead", size, count));
count += numRead;
}
return tmpdata;
@ -451,66 +451,64 @@ public class ZioEntry implements Cloneable {
if (entryOut != null) {
entryOut.close();
size = entryOut.getSize();
data = ((ByteArrayOutputStream)entryOut.getWrappedStream()).toByteArray();
data = ((ByteArrayOutputStream) entryOut.getWrappedStream()).toByteArray();
compressedSize = data.length;
crc32 = entryOut.getCRC();
entryOut = null;
InputStream rawis = new ByteArrayInputStream( data);
InputStream rawis = new ByteArrayInputStream(data);
if (compression == 0) return rawis;
else {
// Hacky, inflate using a sequence of input streams that returns 1 byte more than the actual length of the data.
// This extra dummy byte is required by InflaterInputStream when the data doesn't have the header and crc fields (as it is in zip files).
return new InflaterInputStream( new SequenceInputStream(rawis, new ByteArrayInputStream(new byte[1])), new Inflater( true));
return new InflaterInputStream(new SequenceInputStream(rawis, new ByteArrayInputStream(new byte[1])), new Inflater(true));
}
}
ZioEntryInputStream dataStream;
dataStream = new ZioEntryInputStream(this);
if (monitorStream != null) dataStream.setMonitorStream( monitorStream);
if (monitorStream != null) dataStream.setMonitorStream(monitorStream);
if (compression != 0) {
// Note: When using nowrap=true with Inflater it is also necessary to provide
// an extra "dummy" byte as input. This is required by the ZLIB native library
// in order to support certain optimizations.
dataStream.setReturnDummyByte(true);
return new InflaterInputStream( dataStream, new Inflater( true));
}
else return dataStream;
return new InflaterInputStream(dataStream, new Inflater(true));
} else return dataStream;
}
// Returns an output stream for writing an entry's data.
public OutputStream getOutputStream()
{
entryOut = new ZioEntryOutputStream( compression, new ByteArrayOutputStream());
public OutputStream getOutputStream() {
entryOut = new ZioEntryOutputStream(compression, new ByteArrayOutputStream());
return entryOut;
}
public void write( ZipOutput output) throws IOException {
public void write(ZipOutput output) throws IOException {
boolean debug = getLogger().isDebugEnabled();
output.writeInt( 0x02014b50);
output.writeShort( versionMadeBy);
output.writeShort( versionRequired);
output.writeShort( generalPurposeBits);
output.writeShort( compression);
output.writeShort( modificationTime);
output.writeShort( modificationDate);
output.writeInt( crc32);
output.writeInt( compressedSize);
output.writeInt( size);
output.writeShort( (short)filename.length());
output.writeShort( (short)(extraData.length + numAlignBytes));
output.writeShort( (short)fileComment.length());
output.writeShort( diskNumberStart);
output.writeShort( internalAttributes);
output.writeInt( externalAttributes);
output.writeInt( localHeaderOffset);
output.writeInt(0x02014b50);
output.writeShort(versionMadeBy);
output.writeShort(versionRequired);
output.writeShort(generalPurposeBits);
output.writeShort(compression);
output.writeShort(modificationTime);
output.writeShort(modificationDate);
output.writeInt(crc32);
output.writeInt(compressedSize);
output.writeInt(size);
output.writeShort((short) filename.length());
output.writeShort((short) (extraData.length + numAlignBytes));
output.writeShort((short) fileComment.length());
output.writeShort(diskNumberStart);
output.writeShort(internalAttributes);
output.writeInt(externalAttributes);
output.writeInt(localHeaderOffset);
output.writeString( filename);
output.writeBytes( extraData);
if (numAlignBytes > 0) output.writeBytes( alignBytes, 0, numAlignBytes);
output.writeString( fileComment);
output.writeString(filename);
output.writeBytes(extraData);
if (numAlignBytes > 0) output.writeBytes(alignBytes, 0, numAlignBytes);
output.writeString(fileComment);
}
@ -518,13 +516,13 @@ public class ZioEntry implements Cloneable {
* Returns timetamp in Java format
*/
public long getTime() {
int year = (int)(((modificationDate >> 9) & 0x007f) + 80);
int month = (int)(((modificationDate >> 5) & 0x000f) - 1);
int day = (int)(modificationDate & 0x001f);
int hour = (int)((modificationTime >> 11) & 0x001f);
int minute = (int)((modificationTime >> 5) & 0x003f);
int seconds = (int)((modificationTime << 1) & 0x003e);
Date d = new Date( year, month, day, hour, minute, seconds);
int year = (int) (((modificationDate >> 9) & 0x007f) + 80);
int month = (int) (((modificationDate >> 5) & 0x000f) - 1);
int day = (int) (modificationDate & 0x001f);
int hour = (int) ((modificationTime >> 11) & 0x001f);
int minute = (int) ((modificationTime >> 5) & 0x003f);
int seconds = (int) ((modificationTime << 1) & 0x003e);
Date d = new Date(year, month, day, hour, minute, seconds);
return d.getTime();
}
@ -537,15 +535,14 @@ public class ZioEntry implements Cloneable {
int year = d.getYear() + 1900;
if (year < 1980) {
dtime = (1 << 21) | (1 << 16);
}
else {
} else {
dtime = (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
d.getSeconds() >> 1;
}
modificationDate = (short)(dtime >> 16);
modificationTime = (short)(dtime & 0xFFFF);
modificationDate = (short) (dtime >> 16);
modificationTime = (short) (dtime & 0xFFFF);
}
public boolean isDirectory() {
@ -556,13 +553,15 @@ public class ZioEntry implements Cloneable {
return filename;
}
public void setName( String filename) {
public void setName(String filename) {
this.filename = filename;
}
/** Use 0 (STORED), or 8 (DEFLATE). */
public void setCompression( int compression) {
this.compression = (short)compression;
/**
* Use 0 (STORED), or 8 (DEFLATE).
*/
public void setCompression(int compression) {
this.compression = (short) compression;
}
public short getVersionMadeBy() {

View File

@ -13,18 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/** Input stream used to read just the data from a zip file entry. */
/**
* Input stream used to read just the data from a zip file entry.
*/
public class ZioEntryInputStream extends InputStream {
RandomAccessFile raf;
@ -35,9 +38,9 @@ public class ZioEntryInputStream extends InputStream {
boolean returnDummyByte = false;
OutputStream monitor = null;
public ZioEntryInputStream( ZioEntry entry) throws IOException {
public ZioEntryInputStream(ZioEntry entry) throws IOException {
log = LoggerManager.getLogger( this.getClass().getName());
log = LoggerManager.getLogger(this.getClass().getName());
debug = log.isDebugEnabled();
offset = 0;
size = entry.getCompressedSize();
@ -45,9 +48,8 @@ public class ZioEntryInputStream extends InputStream {
long dpos = entry.getDataPosition();
if (dpos >= 0) {
if (debug) log.debug(String.format("Seeking to %d", entry.getDataPosition()));
raf.seek( entry.getDataPosition());
}
else {
raf.seek(entry.getDataPosition());
} else {
// seeks to, then reads, the local header, causing the
// file pointer to be positioned at the start of the data.
entry.readLocalHeader();
@ -55,7 +57,7 @@ public class ZioEntryInputStream extends InputStream {
}
public void setReturnDummyByte( boolean returnExtraByte) {
public void setReturnDummyByte(boolean returnExtraByte) {
returnDummyByte = returnExtraByte;
}
@ -87,22 +89,20 @@ public class ZioEntryInputStream extends InputStream {
if (returnDummyByte) {
returnDummyByte = false;
return 0;
}
else return -1;
} else return -1;
}
int b = raf.read();
if (b >= 0) {
if (monitor != null) monitor.write(b);
if (debug) log.debug("Read 1 byte");
offset += 1;
}
else if (debug) log.debug("Read 0 bytes");
} else if (debug) log.debug("Read 0 bytes");
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return readBytes( b, off, len);
return readBytes(b, off, len);
}
private int readBytes(byte[] b, int off, int len) throws IOException {
@ -111,10 +111,9 @@ public class ZioEntryInputStream extends InputStream {
returnDummyByte = false;
b[off] = 0;
return 1;
} else return -1;
}
else return -1;
}
int numToRead = Math.min( len, available());
int numToRead = Math.min(len, available());
int numRead = raf.read(b, off, numToRead);
if (numRead > 0) {
if (monitor != null) monitor.write(b, off, numRead);
@ -126,13 +125,13 @@ public class ZioEntryInputStream extends InputStream {
@Override
public int read(byte[] b) throws IOException {
return readBytes( b, 0, b.length);
return readBytes(b, 0, b.length);
}
@Override
public long skip(long n) throws IOException {
long numToSkip = Math.min( n, available());
raf.seek( raf.getFilePointer() + numToSkip);
long numToSkip = Math.min(n, available());
raf.seek(raf.getFilePointer() + numToSkip);
if (debug) log.debug(String.format("Skipped %d bytes", numToSkip));
return numToSkip;
}

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.CRC32;
@ -29,18 +29,17 @@ public class ZioEntryOutputStream extends OutputStream {
OutputStream wrapped;
OutputStream downstream;
public ZioEntryOutputStream( int compression, OutputStream wrapped)
{
public ZioEntryOutputStream(int compression, OutputStream wrapped) {
this.wrapped = wrapped;
if (compression != 0)
downstream = new DeflaterOutputStream( wrapped, new Deflater( Deflater.BEST_COMPRESSION, true));
downstream = new DeflaterOutputStream(wrapped, new Deflater(Deflater.BEST_COMPRESSION, true));
else downstream = wrapped;
}
public void close() throws IOException {
downstream.flush();
downstream.close();
crcValue = (int)crc.getValue();
crcValue = (int) crc.getValue();
}
public int getCRC() {
@ -58,14 +57,14 @@ public class ZioEntryOutputStream extends OutputStream {
}
public void write(byte[] b, int off, int len) throws IOException {
downstream.write( b, off, len);
crc.update( b, off, len);
downstream.write(b, off, len);
crc.update(b, off, len);
size += len;
}
public void write(int b) throws IOException {
downstream.write( b);
crc.update( b);
downstream.write(b);
crc.update(b);
size += 1;
}
@ -73,8 +72,7 @@ public class ZioEntryOutputStream extends OutputStream {
return size;
}
public OutputStream getWrappedStream()
{
public OutputStream getWrappedStream() {
return wrapped;
}

View File

@ -13,15 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@ -30,16 +32,10 @@ import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/**
*
*/
public class ZipInput
{
public class ZipInput implements Closeable {
static LoggerInterface log;
public String inputFilename;
@ -47,14 +43,13 @@ public class ZipInput
long fileLength;
int scanIterations = 0;
Map<String,ZioEntry> zioEntries = new LinkedHashMap<String,ZioEntry>();
Map<String, ZioEntry> zioEntries = new LinkedHashMap<String, ZioEntry>();
CentralEnd centralEnd;
Manifest manifest;
public ZipInput( String filename) throws IOException
{
public ZipInput(String filename) throws IOException {
this.inputFilename = filename;
in = new RandomAccessFile( new File( inputFilename), "r");
in = new RandomAccessFile(new File(inputFilename), "r");
fileLength = in.length();
}
@ -71,32 +66,32 @@ public class ZipInput
return fileLength;
}
public static ZipInput read( String filename) throws IOException {
ZipInput zipInput = new ZipInput( filename);
public static ZipInput read(String filename) throws IOException {
ZipInput zipInput = new ZipInput(filename);
zipInput.doRead();
return zipInput;
}
public ZioEntry getEntry( String filename) {
public ZioEntry getEntry(String filename) {
return zioEntries.get(filename);
}
public Map<String,ZioEntry> getEntries() {
public Map<String, ZioEntry> getEntries() {
return zioEntries;
}
/** Returns the names of immediate children in the directory with the given name.
/**
* Returns the names of immediate children in the directory with the given name.
* The path value must end with a "/" character. Use a value of "/"
* to get the root entries.
*/
public Collection<String> list(String path)
{
public Collection<String> list(String path) {
if (!path.endsWith("/")) throw new IllegalArgumentException("Invalid path -- does not end with '/'");
if (path.startsWith("/")) path = path.substring(1);
Pattern p = Pattern.compile( String.format("^%s([^/]+/?).*", path));
Pattern p = Pattern.compile(String.format("^%s([^/]+/?).*", path));
Set<String> names = new TreeSet<String>();
@ -111,45 +106,46 @@ public class ZipInput
if (manifest == null) {
ZioEntry e = zioEntries.get("META-INF/MANIFEST.MF");
if (e != null) {
manifest = new Manifest( e.getInputStream());
manifest = new Manifest(e.getInputStream());
}
}
return manifest;
}
/** Scan the end of the file for the end of central directory record (EOCDR).
Returns the file offset of the EOCD signature. The size parameter is an
initial buffer size (e.g., 256).
/**
* Scan the end of the file for the end of central directory record (EOCDR).
* Returns the file offset of the EOCD signature. The size parameter is an
* initial buffer size (e.g., 256).
*/
public long scanForEOCDR( int size) throws IOException {
if (size > fileLength || size > 65536) throw new IllegalStateException( "End of central directory not found in " + inputFilename);
public long scanForEOCDR(int size) throws IOException {
if (size > fileLength || size > 65536)
throw new IllegalStateException("End of central directory not found in " + inputFilename);
int scanSize = (int)Math.min( fileLength, size);
int scanSize = (int) Math.min(fileLength, size);
byte[] scanBuf = new byte[scanSize];
in.seek( fileLength - scanSize);
in.seek(fileLength - scanSize);
in.readFully( scanBuf);
in.readFully(scanBuf);
for (int i = scanSize - 22; i >= 0; i--) {
scanIterations += 1;
if (scanBuf[i] == 0x50 && scanBuf[i+1] == 0x4b && scanBuf[i+2] == 0x05 && scanBuf[i+3] == 0x06) {
if (scanBuf[i] == 0x50 && scanBuf[i + 1] == 0x4b && scanBuf[i + 2] == 0x05 && scanBuf[i + 3] == 0x06) {
return fileLength - scanSize + i;
}
}
return scanForEOCDR( size * 2);
return scanForEOCDR(size * 2);
}
private void doRead()
{
private void doRead() {
try {
long posEOCDR = scanForEOCDR( 256);
in.seek( posEOCDR);
centralEnd = CentralEnd.read( this);
long posEOCDR = scanForEOCDR(256);
in.seek(posEOCDR);
centralEnd = CentralEnd.read(this);
boolean debug = getLogger().isDebugEnabled();
if (debug) {
@ -157,32 +153,35 @@ public class ZipInput
getLogger().debug(String.format("Directory entries=%d, size=%d, offset=%d/0x%08x", centralEnd.totalCentralEntries,
centralEnd.centralDirectorySize, centralEnd.centralStartOffset, centralEnd.centralStartOffset));
ZipListingHelper.listHeader( getLogger());
ZipListingHelper.listHeader(getLogger());
}
in.seek( centralEnd.centralStartOffset);
in.seek(centralEnd.centralStartOffset);
for (int i = 0; i < centralEnd.totalCentralEntries; i++) {
ZioEntry entry = ZioEntry.read(this);
zioEntries.put( entry.getName(), entry);
if (debug) ZipListingHelper.listEntry( getLogger(), entry);
zioEntries.put(entry.getName(), entry);
if (debug) ZipListingHelper.listEntry(getLogger(), entry);
}
}
catch (Throwable t) {
} catch (Throwable t) {
t.printStackTrace();
}
}
@Override
public void close() {
if (in != null) try { in.close(); } catch( Throwable t) {}
if (in != null) try {
in.close();
} catch (Throwable t) {
}
}
public long getFilePointer() throws IOException {
return in.getFilePointer();
}
public void seek( long position) throws IOException {
public void seek(long position) throws IOException {
in.seek(position);
}
@ -190,7 +189,7 @@ public class ZipInput
return in.readByte();
}
public int readInt() throws IOException{
public int readInt() throws IOException {
int result = 0;
for (int i = 0; i < 4; i++) {
result |= (in.readUnsignedByte() << (8 * i));
@ -206,7 +205,7 @@ public class ZipInput
return result;
}
public String readString( int length) throws IOException {
public String readString(int length) throws IOException {
byte[] buffer = new byte[length];
for (int i = 0; i < length; i++) {
@ -215,7 +214,7 @@ public class ZipInput
return new String(buffer);
}
public byte[] readBytes( int length) throws IOException {
public byte[] readBytes(int length) throws IOException {
byte[] buffer = new byte[length];
for (int i = 0; i < length; i++) {
@ -224,10 +223,9 @@ public class ZipInput
return buffer;
}
public int read( byte[] b, int offset, int length) throws IOException {
return in.read( b, offset, length);
public int read(byte[] b, int offset, int length) throws IOException {
return in.read(b, offset, length);
}
}

View File

@ -13,31 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import java.util.Date;
import kellinwood.logging.LoggerInterface;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.util.Date;
/**
*
*/
public class ZipListingHelper
{
public class ZipListingHelper {
static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm");
public static void listHeader(LoggerInterface log)
{
public static void listHeader(LoggerInterface log) {
log.debug(" Length Method Size Ratio Date Time CRC-32 Name");
log.debug("-------- ------ ------- ----- ---- ---- ------ ----");
}
public static void listEntry(LoggerInterface log, ZioEntry entry)
{
public static void listEntry(LoggerInterface log, ZioEntry entry) {
int ratio = 0;
if (entry.getSize() > 0) ratio = (100 * (entry.getSize() - entry.getCompressedSize())) / entry.getSize();
log.debug(String.format("%8d %6s %8d %4d%% %s %08x %s",
@ -45,7 +43,7 @@ public class ZipListingHelper
entry.getCompression() == 0 ? "Stored" : "Defl:N",
entry.getCompressedSize(),
ratio,
dateFormat.format( new Date( entry.getTime())),
dateFormat.format(new Date(entry.getTime())),
entry.getCrc32(),
entry.getName()));
}

View File

@ -13,24 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kellinwood.zipio;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import kellinwood.logging.LoggerInterface;
import kellinwood.logging.LoggerManager;
/**
*
*/
public class ZipOutput
{
public class ZipOutput {
static LoggerInterface log;
@ -41,30 +42,26 @@ public class ZipOutput
List<ZioEntry> entriesWritten = new LinkedList<ZioEntry>();
Set<String> namesWritten = new HashSet<String>();
public ZipOutput( String filename) throws IOException
{
public ZipOutput(String filename) throws IOException {
this.outputFilename = filename;
File ofile = new File( outputFilename);
File ofile = new File(outputFilename);
init(ofile);
}
public ZipOutput( File outputFile) throws IOException
{
public ZipOutput(File outputFile) throws IOException {
this.outputFilename = outputFile.getAbsolutePath();
File ofile = outputFile;
init(ofile);
}
private void init( File ofile) throws IOException
{
private void init(File ofile) throws IOException {
if (ofile.exists()) ofile.delete();
out = new FileOutputStream( ofile);
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader( getLogger());
out = new FileOutputStream(ofile);
if (getLogger().isDebugEnabled()) ZipListingHelper.listHeader(getLogger());
}
public ZipOutput( OutputStream os) throws IOException
{
public ZipOutput(OutputStream os) throws IOException {
out = os;
}
@ -73,38 +70,39 @@ public class ZipOutput
return log;
}
public void write( ZioEntry entry) throws IOException {
public void write(ZioEntry entry) throws IOException {
String entryName = entry.getName();
if (namesWritten.contains( entryName)) {
if (namesWritten.contains(entryName)) {
getLogger().warning("Skipping duplicate file in output: " + entryName);
return;
}
entry.writeLocalEntry( this);
entriesWritten.add( entry);
namesWritten.add( entryName);
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry( getLogger(), entry);
entry.writeLocalEntry(this);
entriesWritten.add(entry);
namesWritten.add(entryName);
if (getLogger().isDebugEnabled()) ZipListingHelper.listEntry(getLogger(), entry);
}
public void close() throws IOException
{
public void close() throws IOException {
CentralEnd centralEnd = new CentralEnd();
centralEnd.centralStartOffset = (int)getFilePointer();
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short)entriesWritten.size();
centralEnd.centralStartOffset = (int) getFilePointer();
centralEnd.numCentralEntries = centralEnd.totalCentralEntries = (short) entriesWritten.size();
for (ZioEntry entry : entriesWritten) {
entry.write( this);
entry.write(this);
}
centralEnd.centralDirectorySize = (int)(getFilePointer() - centralEnd.centralStartOffset);
centralEnd.centralDirectorySize = (int) (getFilePointer() - centralEnd.centralStartOffset);
centralEnd.fileComment = "";
centralEnd.write( this);
centralEnd.write(this);
if (out != null) try { out.close(); } catch( Throwable t) {}
if (out != null) try {
out.close();
} catch (Throwable t) {
}
}
public int getFilePointer() throws IOException {
@ -112,42 +110,42 @@ public class ZipOutput
}
public void writeInt( int value) throws IOException{
public void writeInt(int value) throws IOException {
byte[] data = new byte[4];
for (int i = 0; i < 4; i++) {
data[i] = (byte)(value & 0xFF);
data[i] = (byte) (value & 0xFF);
value = value >> 8;
}
out.write( data);
out.write(data);
filePointer += 4;
}
public void writeShort( short value) throws IOException {
public void writeShort(short value) throws IOException {
byte[] data = new byte[2];
for (int i = 0; i < 2; i++) {
data[i] = (byte)(value & 0xFF);
value = (short)(value >> 8);
data[i] = (byte) (value & 0xFF);
value = (short) (value >> 8);
}
out.write( data);
out.write(data);
filePointer += 2;
}
public void writeString( String value) throws IOException {
public void writeString(String value) throws IOException {
byte[] data = value.getBytes();
out.write( data);
out.write(data);
filePointer += data.length;
}
public void writeBytes( byte[] value) throws IOException {
public void writeBytes(byte[] value) throws IOException {
out.write( value);
out.write(value);
filePointer += value.length;
}
public void writeBytes( byte[] value, int offset, int length) throws IOException {
public void writeBytes(byte[] value, int offset, int length) throws IOException {
out.write( value, offset, length);
out.write(value, offset, length);
filePointer += length;
}

View File

@ -7,7 +7,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
@ -511,11 +510,6 @@ public final class AppUpdateStatusManager {
}
break;
}
if (Build.VERSION.SDK_INT < 11 && entry.intent == null) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
entry.intent = PendingIntent.getActivity(context, 0, intent, 0);
}
}
/**

View File

@ -121,7 +121,7 @@ public class FDroidApp extends Application {
private static volatile int timeout = 10000;
// Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle.
private static final org.spongycastle.jce.provider.BouncyCastleProvider SPONGYCASTLE_PROVIDER;
private static final org.bouncycastle.jce.provider.BouncyCastleProvider BOUNCYCASTLE_PROVIDER;
@SuppressWarnings("unused")
BluetoothAdapter bluetoothAdapter;
@ -135,8 +135,8 @@ public class FDroidApp extends Application {
NotificationHelper notificationHelper;
static {
SPONGYCASTLE_PROVIDER = new org.spongycastle.jce.provider.BouncyCastleProvider();
enableSpongyCastle();
BOUNCYCASTLE_PROVIDER = new org.bouncycastle.jce.provider.BouncyCastleProvider();
enableBouncyCastle();
}
private static Theme curTheme = Theme.light;
@ -209,19 +209,19 @@ public class FDroidApp extends Application {
activity.overridePendingTransition(0, 0);
}
public static void enableSpongyCastle() {
Security.addProvider(SPONGYCASTLE_PROVIDER);
public static void enableBouncyCastle() {
Security.addProvider(BOUNCYCASTLE_PROVIDER);
}
public static void enableSpongyCastleOnLollipop() {
public static void enableBouncyCastleOnLollipop() {
if (Build.VERSION.SDK_INT == 21) {
Security.addProvider(SPONGYCASTLE_PROVIDER);
Security.addProvider(BOUNCYCASTLE_PROVIDER);
}
}
public static void disableSpongyCastleOnLollipop() {
public static void disableBouncyCastleOnLollipop() {
if (Build.VERSION.SDK_INT == 21) {
Security.removeProvider(SPONGYCASTLE_PROVIDER.getName());
Security.removeProvider(BOUNCYCASTLE_PROVIDER.getName());
}
}

View File

@ -16,16 +16,10 @@ public class NfcHelper {
private static final String TAG = "NfcHelper";
@TargetApi(14)
private static NfcAdapter getAdapter(Context context) {
if (Build.VERSION.SDK_INT < 14) {
return null;
}
return NfcAdapter.getDefaultAdapter(context.getApplicationContext());
}
@TargetApi(14)
public static boolean setPushMessage(Activity activity, Uri toShare) {
NfcAdapter adapter = getAdapter(activity);
if (adapter != null) {

View File

@ -6,10 +6,10 @@ import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
// aka Android 4.0 aka Ice Cream Sandwich
public class NfcNotEnabledActivity extends ActionBarActivity {
public class NfcNotEnabledActivity extends AppCompatActivity {
/*
* ACTION_NFC_SETTINGS was added in 4.1 aka Jelly Bean MR1 as a
@ -46,12 +46,8 @@ public class NfcNotEnabledActivity extends ActionBarActivity {
final Intent intent = new Intent();
if (Build.VERSION.SDK_INT >= 16) {
doOnJellybean(intent);
} else if (Build.VERSION.SDK_INT >= 14) {
doOnIceCreamSandwich(intent);
} else {
// no NFC support, so nothing to do here
finish();
return;
doOnIceCreamSandwich(intent);
}
startActivity(intent);
finish();

View File

@ -117,7 +117,7 @@ class NotificationHelper {
}
private boolean useStackedNotifications() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
return Build.VERSION.SDK_INT >= 24;
}
/**
@ -481,15 +481,8 @@ class NotificationHelper {
}
private Point getLargeIconSize() {
int w;
int h;
if (Build.VERSION.SDK_INT >= 11) {
w = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
h = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
} else {
w = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
h = w;
}
int w = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
int h = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
return new Point(w, h);
}

View File

@ -5,11 +5,9 @@ import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.view.Display;
import android.widget.ImageView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.encode.Contents;
@ -39,15 +37,9 @@ public class QrGenAsyncTask extends AsyncTask<String, Void, Void> {
Display display = activity.getWindowManager().getDefaultDisplay();
Point outSize = new Point();
int x, y, qrCodeDimension;
/* lame, got to use both the new and old APIs here */
if (Build.VERSION.SDK_INT >= 13) {
display.getSize(outSize);
x = outSize.x;
y = outSize.y;
} else {
x = display.getWidth();
y = display.getHeight();
}
if (x < y) {
qrCodeDimension = x;
} else {

View File

@ -203,10 +203,10 @@ public class RepoUpdater {
throw new UpdateException(downloadedFile + " does not exist!");
}
// Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes
// Due to a bug in Android 5.0 Lollipop, the inclusion of bouncycastle causes
// breakage when verifying the signature of the downloaded .jar. For more
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
FDroidApp.disableSpongyCastleOnLollipop();
FDroidApp.disableBouncyCastleOnLollipop();
JarFile jarFile = new JarFile(downloadedFile, true);
JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
@ -237,7 +237,7 @@ public class RepoUpdater {
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new UpdateException("Error parsing index", e);
} finally {
FDroidApp.enableSpongyCastleOnLollipop();
FDroidApp.enableBouncyCastleOnLollipop();
Utils.closeQuietly(indexInputStream);
if (downloadedFile != null) {
if (!downloadedFile.delete()) {

View File

@ -1,49 +0,0 @@
package org.fdroid.fdroid.compat;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
public abstract class ClipboardCompat {
public abstract String getText();
public static ClipboardCompat create(Context context) {
if (Build.VERSION.SDK_INT >= 11) {
return new HoneycombClipboard(context);
}
return new OldClipboard();
}
@TargetApi(11)
private static class HoneycombClipboard extends ClipboardCompat {
private final ClipboardManager manager;
HoneycombClipboard(Context context) {
this.manager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
}
@Override
public String getText() {
CharSequence text = null;
if (manager.hasPrimaryClip()) {
ClipData data = manager.getPrimaryClip();
if (data.getItemCount() > 0) {
text = data.getItemAt(0).getText();
}
}
return text != null ? text.toString() : null;
}
}
private static class OldClipboard extends ClipboardCompat {
@Override
public String getText() {
return null;
}
}
}

View File

@ -1,21 +0,0 @@
package org.fdroid.fdroid.compat;
import android.net.Uri;
import android.os.Build;
public class UriCompat {
/**
* Uri#getQueryParameter(String) has the following warning:
*
* > Prior to Ice Cream Sandwich, this decoded the '+' character as '+' rather than ' '.
*/
public static String getQueryParameter(Uri uri, String key) {
String value = uri.getQueryParameter(key);
if (value != null && Build.VERSION.SDK_INT < 14) {
value = value.replaceAll("\\+", " ");
}
return value;
}
}

View File

@ -797,7 +797,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
// breakage when verifying the signature of most .jars. For more
// details, check out https://gitlab.com/fdroid/fdroidclient/issues/111.
try {
FDroidApp.disableSpongyCastleOnLollipop();
FDroidApp.disableBouncyCastleOnLollipop();
final InputStream tmpIn = apkJar.getInputStream(aSignedEntry);
byte[] buff = new byte[2048];
//noinspection StatementWithEmptyBody
@ -818,7 +818,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
final Certificate signer = aSignedEntry.getCertificates()[0];
rawCertBytes = signer.getEncoded();
} finally {
FDroidApp.enableSpongyCastleOnLollipop();
FDroidApp.enableBouncyCastleOnLollipop();
}
apkJar.close();

View File

@ -26,7 +26,7 @@ import android.content.pm.PackageManager;
import android.content.pm.Signature;
import org.acra.ACRA;
import org.fdroid.fdroid.Utils;
import org.spongycastle.util.encoders.Hex;
import org.bouncycastle.util.encoders.Hex;
import java.io.ByteArrayOutputStream;
import java.io.File;

View File

@ -5,19 +5,19 @@ import android.util.Log;
import kellinwood.security.zipsigner.ZipSigner;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.x500.X500Name;
import org.spongycastle.asn1.x509.GeneralName;
import org.spongycastle.asn1.x509.GeneralNames;
import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
import org.spongycastle.asn1.x509.Time;
import org.spongycastle.asn1.x509.X509Extension;
import org.spongycastle.cert.X509CertificateHolder;
import org.spongycastle.cert.X509v3CertificateBuilder;
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.OperatorCreationException;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;

View File

@ -9,7 +9,7 @@ import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.spongycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedInputStream;
import java.io.File;

View File

@ -5,12 +5,13 @@ import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.webkit.MimeTypeMap;
import fi.iki.elonen.NanoHTTPD;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
@ -26,10 +27,6 @@ import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.net.ssl.SSLServerSocketFactory;
import fi.iki.elonen.NanoHTTPD;
public class LocalHTTPD extends NanoHTTPD {
private static final String TAG = "LocalHTTPD";
@ -93,10 +90,10 @@ public class LocalHTTPD extends NanoHTTPD {
session.parseBody(new HashMap<String, String>());
} catch (IOException e) {
Log.e(TAG, "An error occured while parsing the POST body", e);
return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
"Internal server error, check logcat on server for details.");
} catch (ResponseException re) {
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
}
return handlePost(session);
@ -111,13 +108,13 @@ public class LocalHTTPD extends NanoHTTPD {
if (!session.getParms().containsKey("repo")) {
Log.e(TAG, "Malformed /request-swap request to local repo HTTP server."
+ " Should have posted a 'repo' parameter.");
return new Response(Response.Status.BAD_REQUEST, MIME_PLAINTEXT,
return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT,
"Requires 'repo' parameter to be posted.");
}
requestSwap(session.getParms().get("repo"));
return new Response(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
}
return new Response("");
return newFixedLengthResponse("");
}
private Response handleGet(IHTTPSession session) {
@ -155,7 +152,7 @@ public class LocalHTTPD extends NanoHTTPD {
SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(
localRepoKeyStore.getKeyStore(),
localRepoKeyStore.getKeyManagers());
makeSecure(factory);
makeSecure(factory, null);
} catch (LocalRepoKeyStore.InitException | IOException e) {
Log.e(TAG, "Could not enable HTTPS", e);
}
@ -209,7 +206,7 @@ public class LocalHTTPD extends NanoHTTPD {
}
}
Response response = serveFile(headers, f, getMimeTypeForFile(uri));
Response response = serveFile(headers, f, getAndroidMimeTypeForFile(uri));
return response != null ? response :
createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
"Error 404, file not found.");
@ -298,19 +295,19 @@ public class LocalHTTPD extends NanoHTTPD {
// Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType, InputStream message) {
Response res = new Response(status, mimeType, message);
Response res = newChunkedResponse(status, mimeType, message);
res.addHeader("Accept-Ranges", "bytes");
return res;
}
// Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType, String message) {
Response res = new Response(status, mimeType, message);
Response res = newFixedLengthResponse(status, mimeType, message);
res.addHeader("Accept-Ranges", "bytes");
return res;
}
private static String getMimeTypeForFile(String uri) {
private static String getAndroidMimeTypeForFile(String uri) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
if (extension != null) {

View File

@ -1,9 +1,6 @@
package org.fdroid.fdroid.net.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothSocket;
import android.os.Build;
import org.fdroid.fdroid.Utils;
import java.io.BufferedInputStream;
@ -32,9 +29,8 @@ public class BluetoothConnection {
return output;
}
@TargetApi(14)
public void open() throws IOException {
if (Build.VERSION.SDK_INT >= 14 && !socket.isConnected()) {
if (!socket.isConnected()) {
// Server sockets will already be connected when they are passed to us,
// client sockets require us to call connect().
socket.connect();

View File

@ -20,6 +20,8 @@
package org.fdroid.fdroid.views;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
@ -55,7 +57,6 @@ import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.ClipboardCompat;
import org.fdroid.fdroid.compat.CursorAdapterCompat;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.data.Repo;
@ -158,13 +159,24 @@ public class ManageReposActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
public String getPrimaryClipAsText() {
CharSequence text = null;
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboardManager.hasPrimaryClip()) {
ClipData data = clipboardManager.getPrimaryClip();
if (data.getItemCount() > 0) {
text = data.getItemAt(0).getText();
}
}
return text != null ? text.toString() : null;
}
private void showAddRepo() {
/*
* If there is text in the clipboard, and it looks like a URL, use that.
* Otherwise use "https://" as default repo string.
*/
ClipboardCompat clipboard = ClipboardCompat.create(this);
String text = clipboard.getText();
String text = getPrimaryClipAsText();
String fingerprint = null;
String username = null;
String password = null;

View File

@ -2,14 +2,12 @@ package org.fdroid.fdroid.views;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.TextView;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.Repo;
@ -24,11 +22,8 @@ public class RepoAdapter extends CursorAdapter {
private EnabledListener enabledListener;
public static RepoAdapter create(Context context, Cursor cursor, int flags) {
if (Build.VERSION.SDK_INT >= 11) {
return new RepoAdapter(context, cursor, flags);
}
return new RepoAdapter(context, cursor);
}
private RepoAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);

View File

@ -15,8 +15,8 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.text.format.DateUtils;
@ -40,7 +40,7 @@ import org.fdroid.fdroid.data.Schema.RepoTable;
import java.util.Locale;
public class RepoDetailsActivity extends ActionBarActivity {
public class RepoDetailsActivity extends AppCompatActivity {
private static final String TAG = "RepoDetailsActivity";
public static final String ARG_REPO_ID = "repo_id";
@ -223,9 +223,7 @@ public class RepoDetailsActivity extends ActionBarActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (Build.VERSION.SDK_INT >= 14) {
prepareNfcMenuItems(menu);
}
return true;
}

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.views;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@ -65,11 +64,9 @@ public class ScreenShotsActivity extends AppCompatActivity {
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(startPosition);
if (Build.VERSION.SDK_INT >= 11) {
// display some nice animation while swiping
viewPager.setPageTransformer(true, new DepthPageTransformer());
}
}
@Override
protected void onResume() {

View File

@ -1,7 +1,6 @@
package org.fdroid.fdroid.views.apps;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@ -10,7 +9,6 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -20,12 +18,10 @@ import android.support.v7.widget.AppCompatImageView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.fdroid.fdroid.R;
import java.util.Random;
@ -34,7 +30,7 @@ import java.util.Random;
* A feature image can have a {@link android.graphics.drawable.Drawable} or a {@link Palette}. If
* a Drawable is available, then it will draw that, otherwise it will attempt to fall back to the
* Palette you gave it. If a Palette is given, it will draw a series of triangles like so:
*
* <p>
* +_----+----_+_----+----_+
* | \_ | _/ | \_ | _/ |
* | \_|_/ | \_|_/ |
@ -42,12 +38,12 @@ import java.util.Random;
* | \_ | _/ | \_ | _/ |
* | \_|_/ | \_|_/ |
* +-----+-----+-----+-----+
*
* <p>
* where each triangle is filled with one of two variations of the {@link Palette#getDominantColor(int)}
* that is chosen randomly. The randomness is first seeded with the colour that has been selected.
* This is so that if this repaints itself in the future, it will have the same unique pattern rather
* than picking a new random pattern each time.
*
* <p>
* It is suggested that you obtain the Palette from the icon of an app.
*/
@SuppressWarnings("LineLength")
@ -143,12 +139,7 @@ public class FeatureImage extends AppCompatImageView {
private int currentAlpha = 255;
private ValueAnimator alphaAnimator = null;
@TargetApi(11)
private void animateColourChange() {
if (Build.VERSION.SDK_INT < 11) {
return;
}
if (alphaAnimator == null) {
alphaAnimator = ValueAnimator.ofInt(0, 255);
} else {
@ -294,12 +285,15 @@ public class FeatureImage extends AppCompatImageView {
private abstract static class ImageLoadingAdapter implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) { }
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) { }
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) { }
public void onLoadingCancelled(String imageUri, View view) {
}
}
}

View File

@ -5,16 +5,15 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.v4.preference.PreferenceFragment;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.EditTextPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.text.TextUtils;
import android.view.WindowManager;
import com.geecko.QuickLyric.view.AppCompatListPreference;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.fdroid.fdroid.AppDetails2;
@ -59,15 +58,14 @@ public class PreferencesFragment extends PreferenceFragment
private FDroidApp fdroidApp;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.preferences);
useTorCheckPref = (CheckBoxPreference) findPreference(Preferences.PREF_USE_TOR);
enableProxyCheckPref = (CheckBoxPreference) findPreference(Preferences.PREF_ENABLE_PROXY);
updateAutoDownloadPref = findPreference(Preferences.PREF_AUTO_DOWNLOAD_INSTALL_UPDATES);
updatePrivilegedExtensionPref = findPreference(Preferences.PREF_UNINSTALL_PRIVILEGED_APP);
AppCompatListPreference languagePref = (AppCompatListPreference) findPreference(Preferences.PREF_LANGUAGE);
ListPreference languagePref = (ListPreference) findPreference(Preferences.PREF_LANGUAGE);
if (Build.VERSION.SDK_INT >= 24) {
PreferenceCategory category = (PreferenceCategory) findPreference("pref_category_display");
category.removePreference(languagePref);
@ -162,8 +160,8 @@ public class PreferencesFragment extends PreferenceFragment
Activity activity = getActivity();
Languages.setLanguage(activity);
RepoProvider.Helper.clearEtags(getContext());
UpdateService.updateNow(getContext());
RepoProvider.Helper.clearEtags(getActivity());
UpdateService.updateNow(getActivity());
Languages.forceChangeLanguage(activity);
}
@ -173,7 +171,7 @@ public class PreferencesFragment extends PreferenceFragment
entrySummary(key);
if (changing
&& currentKeepCacheTime != Preferences.get().getKeepCacheTime()) {
CleanCacheService.schedule(getContext());
CleanCacheService.schedule(getActivity());
}
break;
@ -219,9 +217,9 @@ public class PreferencesFragment extends PreferenceFragment
case Preferences.PREF_KEEP_INSTALL_HISTORY:
CheckBoxPreference p = (CheckBoxPreference) findPreference(key);
if (p.isChecked()) {
InstallHistoryService.register(getContext());
InstallHistoryService.register(getActivity());
} else {
InstallHistoryService.unregister(getContext());
InstallHistoryService.unregister(getActivity());
}
break;
}
@ -310,13 +308,13 @@ public class PreferencesFragment extends PreferenceFragment
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean && (boolean) newValue) {
UpdateService.autoDownloadUpdates(getContext());
UpdateService.autoDownloadUpdates(getActivity());
}
return true;
}
});
if (PrivilegedInstaller.isDefault(getContext())) {
if (PrivilegedInstaller.isDefault(getActivity())) {
updateAutoDownloadPref.setTitle(R.string.update_auto_install);
updateAutoDownloadPref.setSummary(R.string.update_auto_install_summary);
}

View File

@ -16,9 +16,9 @@ import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.Toast;
import com.ashokvarma.bottomnavigation.BadgeItem;
import com.ashokvarma.bottomnavigation.BottomNavigationBar;
import com.ashokvarma.bottomnavigation.BottomNavigationItem;
import com.ashokvarma.bottomnavigation.TextBadgeItem;
import org.fdroid.fdroid.AppDetails2;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus;
@ -28,7 +28,6 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.UriCompat;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.views.ManageReposActivity;
import org.fdroid.fdroid.views.apps.AppListActivity;
@ -67,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
private MainViewAdapter adapter;
private BottomNavigationBar bottomNavigation;
private int selectedMenuId = R.id.whats_new;
private BadgeItem updatesBadge;
private TextBadgeItem updatesBadge;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -90,7 +89,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
pager.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
updatesBadge = new BadgeItem().hide(false);
updatesBadge = new TextBadgeItem().hide(false);
bottomNavigation = (BottomNavigationBar) findViewById(R.id.bottom_navigation);
bottomNavigation.setTabSelectedListener(this)
@ -232,29 +231,29 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
packageName = data.getLastPathSegment();
} else if (path.startsWith("/repository/browse")) {
// http://f-droid.org/repository/browse?fdfilter=search+query
query = UriCompat.getQueryParameter(data, "fdfilter");
query = data.getQueryParameter("fdfilter");
// http://f-droid.org/repository/browse?fdid=packageName
packageName = UriCompat.getQueryParameter(data, "fdid");
packageName = data.getQueryParameter("fdid");
} else if ("/app".equals(data.getPath()) || "/packages".equals(data.getPath())) {
packageName = null;
}
break;
case "details":
// market://details?id=app.id
packageName = UriCompat.getQueryParameter(data, "id");
packageName = data.getQueryParameter("id");
break;
case "search":
// market://search?q=query
query = UriCompat.getQueryParameter(data, "q");
query = data.getQueryParameter("q");
break;
case "play.google.com":
if (path.startsWith("/store/apps/details")) {
// http://play.google.com/store/apps/details?id=app.id
packageName = UriCompat.getQueryParameter(data, "id");
packageName = data.getQueryParameter("id");
} else if (path.startsWith("/store/search")) {
// http://play.google.com/store/search?q=foo
query = UriCompat.getQueryParameter(data, "q");
query = data.getQueryParameter("q");
}
break;
case "apps":
@ -262,8 +261,8 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
case "www.amazon.com":
// amzn://apps/android?p=app.id
// http://amazon.com/gp/mas/dl/android?s=app.id
packageName = UriCompat.getQueryParameter(data, "p");
query = UriCompat.getQueryParameter(data, "s");
packageName = data.getQueryParameter("p");
query = data.getQueryParameter("s");
break;
}
} else if ("fdroid.app".equals(scheme)) {

View File

@ -1,9 +1,9 @@
package org.fdroid.fdroid.views.main;
import android.annotation.TargetApi;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.util.AttributeSet;
import android.widget.FrameLayout;
@ -55,7 +55,7 @@ public class SettingsView extends FrameLayout {
}
if (currentTransaction == null) {
currentTransaction = activity.getSupportFragmentManager().beginTransaction();
currentTransaction = activity.getFragmentManager().beginTransaction();
}
currentTransaction.replace(getId(), new PreferencesFragment(), "preferences-fragment");
@ -73,18 +73,18 @@ public class SettingsView extends FrameLayout {
throw new IllegalArgumentException("Cannot add a SettingsView to activities which are not an AppCompatActivity");
}
Fragment existingFragment = activity.getSupportFragmentManager().findFragmentByTag("preferences-fragment");
Fragment existingFragment = activity.getFragmentManager().findFragmentByTag("preferences-fragment");
if (existingFragment == null) {
return;
}
if (currentTransaction == null) {
currentTransaction = activity.getSupportFragmentManager().beginTransaction();
currentTransaction = activity.getFragmentManager().beginTransaction();
}
currentTransaction.remove(existingFragment);
currentTransaction.commitAllowingStateLoss();
currentTransaction = null;
activity.getSupportFragmentManager().executePendingTransactions();
activity.getFragmentManager().executePendingTransactions();
}
}

View File

@ -11,30 +11,27 @@ import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v14.preference.PreferenceFragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
import android.util.TypedValue;
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.views.hiding.HidingManager;
import java.util.ArrayList;
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
public class PanicPreferencesFragment extends PreferenceFragment implements SharedPreferences
.OnSharedPreferenceChangeListener {
public class PanicPreferencesFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String PREF_EXIT = Preferences.PREF_PANIC_EXIT;
private static final String PREF_APP = "pref_panic_app";
@ -46,8 +43,7 @@ public class PanicPreferencesFragment extends PreferenceFragment implements Shar
private CheckBoxPreference prefHide;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.preferences_panic);
pm = getActivity().getPackageManager();
@ -148,16 +144,16 @@ public class PanicPreferencesFragment extends PreferenceFragment implements Shar
// no panic app set
prefApp.setValue(Panic.PACKAGE_NAME_NONE);
prefApp.setSummary(getString(R.string.panic_app_setting_summary));
if (Build.VERSION.SDK_INT >= 11) {
prefApp.setIcon(null); // otherwise re-setting view resource doesn't work
Drawable icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_cancel);
Drawable icon = ContextCompat.getDrawable(getActivity(), R.drawable.ic_cancel);
TypedValue typedValue = new TypedValue();
Resources.Theme theme = getContext().getTheme();
Resources.Theme theme = getActivity().getTheme();
theme.resolveAttribute(R.attr.appListItem, typedValue, true);
@ColorInt int color = typedValue.data;
icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
prefApp.setIcon(icon);
}
// disable destructive panic actions
prefHide.setEnabled(false);
} else {
@ -165,9 +161,7 @@ public class PanicPreferencesFragment extends PreferenceFragment implements Shar
try {
prefApp.setValue(packageName);
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
if (Build.VERSION.SDK_INT >= 11) {
prefApp.setIcon(pm.getApplicationIcon(packageName));
}
prefHide.setEnabled(true);
} catch (PackageManager.NameNotFoundException e) {
// revert back to no app, just to be safe
@ -194,7 +188,7 @@ public class PanicPreferencesFragment extends PreferenceFragment implements Shar
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getString(R.string.panic_app_dialog_title));
CharSequence app = getString(R.string.panic_app_unknown_app);
@ -226,10 +220,10 @@ public class PanicPreferencesFragment extends PreferenceFragment implements Shar
private void showHideConfirmationDialog() {
String appName = getString(R.string.app_name);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.panic_hide_warning_title);
builder.setMessage(getString(R.string.panic_hide_warning_message, appName,
HidingManager.getUnhidePin(getContext()), getString(R.string.hiding_calculator)));
HidingManager.getUnhidePin(getActivity()), getString(R.string.hiding_calculator)));
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {

View File

@ -7,7 +7,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.LightingColorFilter;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
@ -148,7 +147,6 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner
qrUrlBuilder.append(sharingUri.getPath());
boolean first = true;
if (Build.VERSION.SDK_INT > 10) {
Set<String> names = sharingUri.getQueryParameterNames();
for (String name : names) {
if (!"ssid".equals(name)) {
@ -163,7 +161,6 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner
qrUrlBuilder.append(sharingUri.getQueryParameter(name).toUpperCase(Locale.ENGLISH));
}
}
}
String qrUriString = qrUrlBuilder.toString();
Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString);

View File

@ -4,7 +4,7 @@ import android.content.Context;
public abstract class CameraCharacteristicsChecker {
public static CameraCharacteristicsChecker getInstance(final Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
if (android.os.Build.VERSION.SDK_INT >= 21) {
return new CameraCharacteristicsMinApiLevel21(context);
} else {
return new CameraCharacteristicsMaxApiLevel20();

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SwapTheme.Wizard" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorButtonNormal">@color/swap_bright_blue</item>
<item name="android:actionBarStyle">@style/Widget.AppCompat.ActionBar.Solid</item>
<item name="android:actionButtonStyle">@style/SwapTheme.Wizard.ActionButton</item>
</style>
<style name="AlertDialogBaseThemeDark" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
<item name="android:actionOverflowButtonStyle">@style/ActionButtonOverflow</item>
</style>
<style name="AlertDialogBaseThemeLight" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
<item name="android:actionOverflowButtonStyle">@style/ActionButtonOverflow</item>
</style>
<style name="AppDetailsLink" parent="AppDetailsLinkBase">
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
</resources>

View File

@ -23,6 +23,7 @@
<item name="appListItem">#ffffff</item>
<item name="lightGrayTextColor">#a6a6a6</item>
<item name="antiFeaturesWarning">@drawable/ic_warning_white_24dp</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
<style name="AppBaseThemeLight" parent="Theme.AppCompat.Light.NoActionBar">
@ -47,6 +48,7 @@
<item name="appListItem">#424242</item>
<item name="lightGrayTextColor">#4a4a4a</item>
<item name="antiFeaturesWarning">@drawable/ic_warning_black_24dp</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
<style name="AppBaseThemeNight" parent="AppThemeDark">

View File

@ -23,7 +23,7 @@
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="@string/updates">
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/update_interval"
<ListPreference android:title="@string/update_interval"
android:key="updateInterval"
android:defaultValue="24"
android:entries="@array/updateIntervalNames"
@ -41,9 +41,9 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/display"
android:key="pref_category_display">
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/pref_language"
<ListPreference android:title="@string/pref_language"
android:key="language"/>
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/theme"
<ListPreference android:title="@string/theme"
android:key="theme"
android:defaultValue="light"
android:entries="@array/themeNames"
@ -119,7 +119,7 @@
<PreferenceCategory android:title="@string/other"
android:key="pref_category_other">
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/cache_downloaded"
<ListPreference android:title="@string/cache_downloaded"
android:key="keepCacheFor"
android:defaultValue="86400000"
android:entries="@array/keepCacheNames"

View File

@ -23,7 +23,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class AntiFeaturesTest extends FDroidProviderTest {

View File

@ -16,7 +16,7 @@ import java.util.List;
/**
* @author Michael Poehn (michael.poehn@fsfe.org)
*/
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class ProvisionerTest {

View File

@ -17,7 +17,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class UtilsTest {

View File

@ -28,7 +28,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class ApkProviderTest extends FDroidProviderTest {

View File

@ -15,7 +15,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class AppPrefsProviderTest extends FDroidProviderTest {

View File

@ -29,7 +29,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class AppProviderTest extends FDroidProviderTest {

View File

@ -19,7 +19,7 @@ import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContentResolver;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class DatabaseMigration {

View File

@ -23,7 +23,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class InstalledAppProviderTest extends FDroidProviderTest {

View File

@ -13,7 +13,7 @@ import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class PreferredSignatureTest extends FDroidProviderTest {

View File

@ -20,7 +20,7 @@ import java.util.List;
import static org.fdroid.fdroid.Assert.assertInvalidUri;
import static org.fdroid.fdroid.Assert.assertValidUri;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class ProviderUriTests {

View File

@ -42,7 +42,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class RepoProviderTest extends FDroidProviderTest {

View File

@ -16,7 +16,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class SuggestedVersionTest extends FDroidProviderTest {

View File

@ -15,7 +15,7 @@ import java.net.URL;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class HttpDownloaderTest {

View File

@ -19,7 +19,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
private static final String TAG = "AcceptableMultiRepoTest";

View File

@ -14,7 +14,7 @@ import org.robolectric.annotation.Config;
* because there is so much metadata to parse in the main repo, covering many different aspects
* of the available metadata. Some apps will be added, others updated, and it should all just work.
*/
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
public class FDroidRepoUpdateTest extends MultiRepoUpdaterTest {

View File

@ -55,7 +55,7 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
public class IndexV1UpdaterTest extends FDroidProviderTest {
public static final String TAG = "IndexV1UpdaterTest";

View File

@ -18,7 +18,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class Issue763MultiRepo extends MultiRepoUpdaterTest {

View File

@ -34,9 +34,8 @@ import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Config(constants = BuildConfig.class, sdk = 24, shadows = ProperMultiRepoUpdaterTest.ArmSystemProperties.class)
@Config(constants = BuildConfig.class, shadows = ProperMultiRepoUpdaterTest.ArmSystemProperties.class)
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
private static final String TAG = "ProperMultiRepoSupport";
@ -136,7 +135,8 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
// Provided by both the "Main" and "Conflicting" repo, so need to fetch metdata from the
// repo with the higher "Conflicting" repo has a higher priority.
App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.adaway");
App adAway = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"org.adaway");
assertAdAwayMetadata(adAway, higherPriority);
assertAdAwayMetadata(allApps.get("org.adaway"), higherPriority);
@ -144,17 +144,20 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
// This is only provided by the "Main" or "Archive" repo. Both the main and archive repo both
// pull their metadata from the same build recipe in fdroidserver. The only difference is that
// the archive repository contains .apks from further back, but their metadata is the same.
App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "com.uberspot.a2048");
App a2048 = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"com.uberspot.a2048");
assert2048Metadata(a2048, "Normal");
assert2048Metadata(allApps.get("com.uberspot.a2048"), "Normal");
// This is only provided by the "Conflicting" repo.
App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "org.dgtale.icsimport");
App calendar = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"org.dgtale.icsimport");
assertCalendarMetadata(calendar, "Conflicting");
assertCalendarMetadata(allApps.get("org.dgtale.icsimport"), "Conflicting");
// This is only provided by the "Main" repo.
App adb = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(), "siir.es.adbWireless");
App adb = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
"siir.es.adbWireless");
assertAdbMetadata(adb, "Normal");
assertAdbMetadata(allApps.get("siir.es.adbWireless"), "Normal");
}
@ -244,7 +247,8 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
}
private void assertCanUpdate(String packageName, int installedVersion, int expectedUpdateVersion) {
InstalledAppTestUtils.install(context, packageName, installedVersion, "v" + installedVersion, TestUtils.FDROID_CERT);
InstalledAppTestUtils.install(context, packageName, installedVersion,
"v" + installedVersion, TestUtils.FDROID_CERT);
List<App> appsToUpdate = AppProvider.Helper.findCanUpdate(context, AppMetadataTable.Cols.ALL);
assertEquals(1, appsToUpdate.size());
assertEquals(installedVersion, appsToUpdate.get(0).installedVersionCode);
@ -281,7 +285,8 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
}
private void assert2048Metadata(Repo repo, @RepoIdentifier String id) {
App a2048 = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.uberspot.a2048", repo.getId(), AppMetadataTable.Cols.ALL);
App a2048 = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.uberspot.a2048",
repo.getId(), AppMetadataTable.Cols.ALL);
assert2048Metadata(a2048, id);
}
@ -300,26 +305,36 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
}
private void assertAdAwayMetadata(Repo repo, @RepoIdentifier String id) {
App adaway = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.adaway", repo.getId(), AppMetadataTable.Cols.ALL);
App adaway = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.adaway",
repo.getId(), AppMetadataTable.Cols.ALL);
assertAdAwayMetadata(adaway, id);
}
/** @see ProperMultiRepoUpdaterTest#assert2048Metadata(Repo, String) */
private void assertAdAwayMetadata(App adaway, @RepoIdentifier String id) {
assertNotNull(adaway);
assertEquals(String.format("AdAway", id), adaway.name);
assertEquals(String.format("<p>AdAway from %s repo.</p>", id), adaway.description);
assertEquals(String.format("Block advertisements (%s)", id), adaway.summary);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.webSite);
assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id), adaway.sourceCode);
assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id), adaway.issueTracker);
assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id), adaway.changelog);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id), adaway.donate);
assertEquals(String.format("AdAway", id),
adaway.name);
assertEquals(String.format("<p>AdAway from %s repo.</p>", id),
adaway.description);
assertEquals(String.format("Block advertisements (%s)", id),
adaway.summary);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id),
adaway.webSite);
assertEquals(String.format("https://github.com/dschuermann/ad-away?%s", id),
adaway.sourceCode);
assertEquals(String.format("https://github.com/dschuermann/ad-away/issues?%s", id),
adaway.issueTracker);
assertEquals(String.format("https://github.com/dschuermann/ad-away/raw/HEAD/CHANGELOG?%s", id),
adaway.changelog);
assertEquals(String.format("http://sufficientlysecure.org/index.php/adaway?%s", id),
adaway.donate);
assertEquals(String.format("369138", id), adaway.flattrID);
}
private void assertAdbMetadata(Repo repo, @RepoIdentifier String id) {
App adb = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "siir.es.adbWireless", repo.getId(), AppMetadataTable.Cols.ALL);
App adb = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "siir.es.adbWireless",
repo.getId(), AppMetadataTable.Cols.ALL);
assertAdbMetadata(adb, id);
}
@ -335,20 +350,28 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
}
private void assertCalendarMetadata(Repo repo, @RepoIdentifier String id) {
App calendar = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.dgtale.icsimport", repo.getId(), AppMetadataTable.Cols.ALL);
App calendar = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.dgtale.icsimport",
repo.getId(), AppMetadataTable.Cols.ALL);
assertCalendarMetadata(calendar, id);
}
/** @see ProperMultiRepoUpdaterTest#assert2048Metadata(Repo, String) */
private void assertCalendarMetadata(App calendar, @RepoIdentifier String id) {
assertNotNull(calendar);
assertEquals("Add to calendar", calendar.name);
assertEquals(String.format("<p>Add to calendar from %s repo.</p>", id), calendar.description);
assertEquals(String.format("Import .ics files into calendar (%s)", id), calendar.summary);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id), calendar.webSite);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id), calendar.sourceCode);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id), calendar.issueTracker);
assertEquals("2225390", calendar.flattrID);
assertEquals("Add to calendar",
calendar.name);
assertEquals(String.format("<p>Add to calendar from %s repo.</p>", id),
calendar.description);
assertEquals(String.format("Import .ics files into calendar (%s)", id),
calendar.summary);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/blob/HEAD/README.md?%s", id),
calendar.webSite);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport?%s", id),
calendar.sourceCode);
assertEquals(String.format("https://github.com/danielegobbetti/ICSImport/issues?%s", id),
calendar.issueTracker);
assertEquals("2225390",
calendar.flattrID);
}
private void assertMainArchiveRepoMetadata() {
@ -422,7 +445,7 @@ public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
if ("ro.product.cpu.abilist".equals(key)) {
return "armeabi";
}
return ShadowSystemProperties.get(key);
return ShadowSystemProperties.native_get(key);
}
}

View File

@ -53,7 +53,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, sdk = 24)
@Config(constants = BuildConfig.class)
@RunWith(RobolectricTestRunner.class)
public class RepoXMLHandlerTest {
private static final String TAG = "RepoXMLHandlerTest";

View File

@ -27,7 +27,7 @@ import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricTestRunner.class)
public class AppDetailsAdapterTest extends FDroidProviderTest {

View File

@ -1,10 +1,22 @@
buildscript {
repositories {
maven {
url "https://repo1.maven.org/maven2"
jcenter()
}
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
// 2.2.2 is the version that is included in Debian/stretch
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.android.tools.build:gradle:3.1.1'
classpath files('libs/gradle-witness.jar')
}
}
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}

View File

@ -142,4 +142,7 @@
<property name="influenceFormat" value="1"/>
</module>
<module name="SuppressionFilter">
<property name="file" value="config/checkstyle/suppressions.xml"/>
</module>
</module>

View File

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress checks="." files="[\\/]kellinwood[\\/].*\.java$"/>
</suppressions>

View File

@ -1,12 +0,0 @@
Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the NanoHttpd organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,14 +0,0 @@
apply plugin: 'java'
version = '2.1.0'
sourceCompatibility = 1.7
targetCompatibility = 1.7
jar {
baseName = 'nanohttpd'
}
repositories {
mavenCentral()
}

Some files were not shown because too many files have changed in this diff Show More