org.dyndns.jkiddo.dmp.util.DmapUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.dyndns.jkiddo.dmp.util.DmapUtil.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Jens Kristian Villadsen.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Jens Kristian Villadsen - Lead developer, owner and creator
 ******************************************************************************/
/*
 * Digital Audio Access Protocol (DAAP) Library
 * Copyright (C) 2004-2010 Roger Kapsi
 *
 * 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 org.dyndns.jkiddo.dmp.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.zip.GZIPOutputStream;

import org.dyndns.jkiddo.dmp.DmapOutputStream;
import org.dyndns.jkiddo.dmp.chunks.Chunk;

import com.google.common.io.Resources;

/**
 * Misc methods and constants
 * 
 * @author Roger Kapsi
 */
public final class DmapUtil {
    /**
     * NULL value (Zero) is a forbidden value (in some cases) in DAAP and means that a value is not initialized (basically <code>null</code> for primitive types).
     */
    public static final int NULL = 0;

    /**
     * Global flag to turn gzip compression on and off
     */
    public static final boolean COMPRESS = true;

    /** ISO Latin 1 encoding */
    public static final String ISO_8859_1 = "ISO-8859-1";

    /** UTF-8 encoding */
    public static final String UTF_8 = "UTF-8";

    /** "\r\n" <b>DON'T TOUCH!</b> */
    static final byte[] CRLF = { (byte) '\r', (byte) '\n' };

    private final static SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy hh:mm:ss z", Locale.US);

    /** Music Sharing Version 2.0.1 */
    //public static final int MUSIC_SHARING_VERSION_309 = 0x00020000;

    /** Music Sharing Version 3.0.11 */
    public static final int MUSIC_SHARING_VERSION_3011 = 0x0003000B;

    /** Music Sharing Version 3.0.10 */
    public static final int MUSIC_SHARING_VERSION_3010 = 0x0003000A;
    /**
     * NEWEST BELOW
     */

    /** MPRO Version 2.0.10 (iTunes 11.1.5.5) */
    public static final int MPRO_VERSION_2010 = 0x0002000A;

    public static final int CONTROL_VERSION_205 = 0x00020005;

    /** PPRO Version 2.0.1 (iTunes 11.1.5.5) */
    public static final int PPRO_VERSION_201 = 0x00020001;

    /** APRO Version 3.0.12 (iTunes 11.1.5.5) */
    public static final int APRO_VERSION_3012 = 0x0003000C;

    /** MPRO Version 2.0.9 (iTunes 11.0.1.12) */
    // public static final int MPRO_VERSION_209 = 0x00020009;

    /** PPRO Version 2.0.0 (iTunes 11.0.1.12) */
    // public static final int PPRO_VERSION_200 = 0x00020000;

    /** APRO Version 3.0.11 (iTunes 11.0.1.12) */
    // public static final int APRO_VERSION_3011 = 0x0003000B;

    /** PPRO Version 1.0.1 (iPhoto 9.4.3) */
    public static final int PPRO_VERSION_101 = 0x00010001;

    /** MPRO Version 2.0.0 (iPhoto 9.4.3) */
    public static final int MPRO_VERSION_200 = 0x00020000;

    /** Default DAAP realm */
    public static final String DAAP_REALM = "daap";

    public static final String DEFAULT_USER = "iTunes_12.1";

    private DmapUtil() {
    }

    /**
     * Returns <code>true</code> if version is a supported protocol version. At the moment only {@see #VERSION_3} and later are supported.
     * 
     * @param version
     *            a protocol version
     * @return <code>true</code> if version is a supported
     */
    // public static boolean isSupportedProtocolVersion(int version)
    // {
    // if(version >= DAAP_VERSION_3)
    // {
    // return true;
    // }
    // return false;
    // }
    //
    /**
     * Converts a four character content code to an int and returns it.
     * 
     * @param contentCode
     *            a four character content code
     * @return content code
     */
    public static int toContentCodeNumber(final String contentCode) {
        if (contentCode.length() != 4) {
            throw new IllegalArgumentException("content code must have 4 characters!");
        }

        return ((contentCode.charAt(0) & 0xFF) << 24) | ((contentCode.charAt(1) & 0xFF) << 16)
                | ((contentCode.charAt(2) & 0xFF) << 8) | ((contentCode.charAt(3) & 0xFF));
    }

    /**
     * Converts an four byte int to a string
     */
    public static String toContentCodeString(final int contentCode) {
        final char[] code = new char[4];
        code[0] = (char) ((contentCode >> 24) & 0xFF);
        code[1] = (char) ((contentCode >> 16) & 0xFF);
        code[2] = (char) ((contentCode >> 8) & 0xFF);
        code[3] = (char) ((contentCode) & 0xFF);
        return new String(code);
    }

    /**
     * Returns the current Date/Time in "iTunes time format"
     */
    public static final String now() {
        return formatter.format(new Date());
    }

    /**
     * Serializes the <code>chunk</code> and compresses it optionally. The serialized data is returned as a byte-Array.
     */
    public static final byte[] serialize(final Chunk chunk, final boolean compress) throws IOException {
        final ByteArrayOutputStream buffer = new ByteArrayOutputStream(8192);
        DmapOutputStream out = null;

        if (DmapUtil.COMPRESS && compress) {
            final GZIPOutputStream gzip = new GZIPOutputStream(buffer);
            out = new DmapOutputStream(gzip);
        } else {
            out = new DmapOutputStream(buffer);
        }

        out.writeChunk(chunk);
        out.close();

        return buffer.toByteArray();
    }

    public static final Collection<String> parseMeta(final String meta) {
        if (meta == null)
            return Collections.emptyList();

        return Collections.unmodifiableCollection(moveItemIdInSecond(createTokenList(meta)));
    }

    private static List<String> moveItemIdInSecond(final List<String> list) {
        final boolean noItemKind = moveItemKindInFirst(list);
        if (list.contains("dmap.itemid")) {
            if (list.size() > 1) {
                list.remove("dmap.itemid");
                int index = 1;
                if (noItemKind) {
                    index = 0;
                }

                list.add(index, "dmap.itemid");
            }
        }
        return list;
    }

    private static boolean moveItemKindInFirst(final List<String> list) {
        boolean noItemKind = true;
        if (list.contains("dmap.itemkind")) {
            list.remove("dmap.itemkind");
            list.add(0, "dmap.itemkind");
            noItemKind = false;
        }
        return noItemKind;
    }

    private static List<String> createTokenList(final String meta) {
        final StringTokenizer tokens = new StringTokenizer(meta, ",");
        final List<String> list = new ArrayList<String>(tokens.countTokens());

        while (tokens.hasMoreTokens()) {
            final String token = tokens.nextToken();
            // Must be the fist! See DAAP documentation for more info!
            list.add(token);
        }
        return list;
    }

    /**
     * Converts major, minor to a DMAP version. Version 2 is for example 0x00020000
     * 
     * @param major
     *            the major version (x)
     * @return x.0.0
     */
    public static int toVersion(final int major) {
        return toVersion(major, 0, 0);
    }

    /**
     * Converts major, minor to a DMAP version. Version 2.1 is for example 0x00020100
     * 
     * @param major
     *            the major version (x)
     * @param minor
     *            the minor version (y)
     * @return x.y.0
     */
    public static int toVersion(final int major, final int minor) {
        return toVersion(major, minor, 0);
    }

    /**
     * Converts major, minor and patch to a DMAP version. Version 2.1.3 is for example 0x00020103
     * 
     * @param major
     *            the major version (x)
     * @param minor
     *            the minor version (y)
     * @param micro
     *            the patch version (z)
     * @return x.y.z
     */
    public static int toVersion(final int major, final int minor, final int micro) {
        return (major & 0xFFFF) << 16 | (minor & 0xFF) << 8 | (micro & 0xFF);
    }
    //
    // /**
    // * This method tries the determinate the protocol version and returns it or {@see #NULL} if version could not be estimated...
    // */
    // public static int getProtocolVersion(DaapRequest request)
    // {
    //
    // if(request.isUnknownRequest())
    // return DaapUtil.NULL;
    //
    // Header header = request.getHeader(DaapRequest.CLIENT_DAAP_VERSION);
    //
    // if(header == null && request.isSongRequest())
    // {
    // header = request.getHeader(DaapRequest.USER_AGENT);
    // }
    //
    // if(header == null)
    // return DaapUtil.NULL;
    //
    // String name = header.getName();
    // String value = header.getValue();
    //
    // // Unfortunately song requests do not have a Client-DAAP-Version
    // // header. As a workaround we can estimate the protocol version
    // // by User-Agent but that is weak an may break with non iTunes
    // // hosts...
    // if(request.isSongRequest() && name.equals(DaapRequest.USER_AGENT))
    // {
    //
    // // Note: the protocol version of a Song request is estimated
    // // by the server with the aid of the sessionId, i.e. this block
    // // is actually never touched...
    // if(value.startsWith("iTunes/5.0"))
    // {
    // return DaapUtil.DAAP_VERSION_302;
    // }
    // else if(value.startsWith("iTunes/4.9") || value.startsWith("iTunes/4.8") || value.startsWith("iTunes/4.7") || value.startsWith("iTunes/4.6") || value.startsWith("iTunes/4.5"))
    // {
    // return DaapUtil.DAAP_VERSION_3;
    // }
    // else if(value.startsWith("iTunes/4.2") || value.startsWith("iTunes/4.1"))
    // {
    // return DaapUtil.DAAP_VERSION_2;
    // }
    // else if(value.startsWith("iTunes/4.0"))
    // {
    // return DaapUtil.DAAP_VERSION_1;
    // }
    // else
    // {
    // return DaapUtil.NULL;
    // }
    // }
    // StringTokenizer tokenizer = new StringTokenizer(value, ".");
    // int count = tokenizer.countTokens();
    //
    // if(count >= 2 && count <= 3)
    // {
    // try
    // {
    //
    // int major = DaapUtil.NULL;
    // int minor = DaapUtil.NULL;
    // int patch = DaapUtil.NULL;
    //
    // major = Integer.parseInt(tokenizer.nextToken());
    // minor = Integer.parseInt(tokenizer.nextToken());
    //
    // if(count == 3)
    // patch = Integer.parseInt(tokenizer.nextToken());
    //
    // return DaapUtil.toVersion(major, minor, patch);
    //
    // }
    // catch(NumberFormatException err)
    // {}
    // }
    //
    // return DaapUtil.NULL;
    // }
    //
    // /**
    // *
    // */
    // public static long parseUInt(String value) throws NumberFormatException
    // {
    // try
    // {
    // return UIntChunk.checkUIntRange(Long.parseLong(value));
    // }
    // catch(IllegalArgumentException err)
    // {
    // throw new NumberFormatException("For input: " + value);
    // }
    // }
    //
    // /**
    // * Generates a random int
    // */
    // public static int nextInt()
    // {
    // synchronized(generator)
    // {
    // return generator.nextInt();
    // }
    // }
    //
    // /**
    // * Generates a random int
    // */
    // public static int nextInt(int max)
    // {
    // synchronized(generator)
    // {
    // return generator.nextInt(max);
    // }
    // }
    //
    /**
     * String to byte Array
     */

    // public static byte[] getBytes(String s, String charsetName)
    // {
    // try
    // {
    // return s.getBytes(charsetName);
    // }
    // catch(UnsupportedEncodingException e)
    // {
    // // should never happen
    // throw new RuntimeException(e);
    // }
    // }
    //
    // /**
    // * Byte Array to String
    // */
    // public static String toString(byte[] b, String charsetName)
    // {
    // try
    // {
    // return new String(b, charsetName);
    // }
    // catch(UnsupportedEncodingException e)
    // {
    // // should never happen
    // throw new RuntimeException(e);
    // }
    // }
    //
    // /**
    // * Returns b as hex String
    // */
    // public static String toHexString(byte[] b)
    // {
    // if(b.length % 2 != 0)
    // {
    // throw new IllegalArgumentException("Argument's length must be power of 2");
    // }
    //
    // StringBuffer buffer = new StringBuffer(b.length * 2);
    // for(int i = 0; i < b.length; i++)
    // {
    // char hi = HEX[((b[i] >> 4) & 0xF)];
    // char lo = HEX[b[i] & 0xF];
    //
    // buffer.append(hi).append(lo);
    // }
    // return buffer.toString();
    // }
    //
    // public static byte[] parseHexString(String s)
    // {
    // if(s.length() % 2 != 0)
    // {
    // throw new IllegalArgumentException("Argument's length() must be power of 2");
    // }
    //
    // byte[] buffer = new byte[s.length() / 2];
    // for(int i = 0, j = 0; i < buffer.length; i++)
    // {
    // buffer[i] = (byte) ((parseHexToInt(s.charAt(j++) & 0xFF) << 0x4) | parseHexToInt(s.charAt(j++) & 0xFF));
    // }
    // return buffer;
    // }
    //
    // private static int parseHexToInt(int hex)
    // {
    // switch(hex)
    // {
    // case '0':
    // return 0;
    // case '1':
    // return 1;
    // case '2':
    // return 2;
    // case '3':
    // return 3;
    // case '4':
    // return 4;
    // case '5':
    // return 5;
    // case '6':
    // return 6;
    // case '7':
    // return 7;
    // case '8':
    // return 8;
    // case '9':
    // return 9;
    // case 'A':
    // return 10;
    // case 'a':
    // return 10;
    // case 'B':
    // return 11;
    // case 'b':
    // return 11;
    // case 'C':
    // return 12;
    // case 'c':
    // return 12;
    // case 'D':
    // return 13;
    // case 'd':
    // return 13;
    // case 'E':
    // return 14;
    // case 'e':
    // return 14;
    // case 'F':
    // return 15;
    // case 'f':
    // return 15;
    // default:
    // throw new NumberFormatException("'" + Character.toString((char) hex) + "'");
    // }
    // }
    //
    /**
     * Creates a random nonce
     */

    public static String nonce() throws NoSuchAlgorithmException, UnsupportedEncodingException {

        return encode(MessageDigest.getInstance("MD5")
                .digest(Long.toString(System.currentTimeMillis()).getBytes("US-ASCII")));
    }

    private static String encode(final byte[] binaryData) {

        if (binaryData.length != 16) {
            return null;
        }

        final char[] buffer = new char[32];
        for (int i = 0; i < 16; i++) {
            final int low = binaryData[i] & 0x0f;
            final int high = (binaryData[i] & 0xf0) >> 4;
            buffer[i * 2] = HEXADECIMAL[high];
            buffer[(i * 2) + 1] = HEXADECIMAL[low];
        }

        return new String(buffer);
    }

    private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
            'd', 'e', 'f' };
    //
    // public static byte[] toMD5(String s)
    // {
    // try
    // {
    // return MessageDigest.getInstance("MD5").digest(getBytes(s, ISO_8859_1));
    // }
    // catch(NoSuchAlgorithmException err)
    // {
    // // should never happen
    // throw new RuntimeException(err);
    // }
    // }
    //
    // public static String calculateHA1(String username, String password)
    // {
    // return calculateHA1(username, getBytes(password, ISO_8859_1));
    // }
    //
    // public static String calculateHA1(String username, byte[] password)
    // {
    // try
    // {
    // MessageDigest md = MessageDigest.getInstance("MD5");
    // md.update(getBytes(username, ISO_8859_1));
    // md.update((byte) ':');
    // md.update(getBytes(DAAP_REALM, ISO_8859_1));
    // md.update((byte) ':');
    // // md.update(getBytes(password, ISO_8859_1));
    // md.update(password);
    // return toHexString(md.digest());
    // }
    // catch(NoSuchAlgorithmException err)
    // {
    // // should never happen
    // throw new RuntimeException(err);
    // }
    // }
    //
    // public static String calculateHA2(String uri)
    // {
    // try
    // {
    // MessageDigest md = MessageDigest.getInstance("MD5");
    //
    // md.update(getBytes("GET", ISO_8859_1));
    // md.update((byte) ':');
    // md.update(getBytes(uri, ISO_8859_1));
    // return toHexString(md.digest());
    // }
    // catch(NoSuchAlgorithmException err)
    // {
    // // should never happen
    // throw new RuntimeException(err);
    // }
    // }
    //
    // public static String digest(String ha1, String ha2, String nonce)
    // {
    // try
    // {
    // MessageDigest md = MessageDigest.getInstance("MD5");
    //
    // md.update(getBytes(ha1, ISO_8859_1));
    // md.update((byte) ':');
    // md.update(getBytes(nonce, ISO_8859_1));
    // md.update((byte) ':');
    // md.update(getBytes(ha2, ISO_8859_1));
    // return toHexString(md.digest());
    // }
    // catch(NoSuchAlgorithmException err)
    // {
    // // should never happen
    // throw new RuntimeException(err);
    // }
    // }
    //
    // // see org.apache.commons.httpclient.auth.DigestScheme
    // /*
    // * public static String digest(String username, byte[] password, String nonce, String uri) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(getBytes(username, ISO_8859_1)); md.update((byte)':'); md.update(getBytes(DAAP_REALM, ISO_8859_1)); md.update((byte)':'); //md.update(getBytes(password, ISO_8859_1)); md.update(password); final String HA1 = toHexString(md.digest()); md.reset(); md.update(getBytes("GET", ISO_8859_1)); md.update((byte)':'); md.update(getBytes(uri, ISO_8859_1)); final String HA2 = toHexString(md.digest()); md.reset(); md.update(getBytes(HA1, ISO_8859_1)); md.update((byte)':'); md.update(getBytes(nonce, ISO_8859_1)); md.update((byte)':'); md.update(getBytes(HA2, ISO_8859_1)); return toHexString(md.digest()); } catch (NoSuchAlgorithmException
    // err)
    // * { // should never happen throw new RuntimeException(err); } } /** Returns the extension of file or null if file has no extension
    // */
    // public static String getExtension(File file)
    // {
    // return file.isFile() ? getExtension(file.getName()) : null;
    // }
    //
    // /**
    // * Returns the extension of fileName or null if file has no extension
    // */
    // public static String getExtension(String fileName)
    // {
    // int p = fileName.lastIndexOf('.');
    // if(p != -1 && ++p < fileName.length())
    // {
    // return fileName.substring(p).toLowerCase(Locale.US);
    // }
    // return null;
    // }
    //
    // /**
    // * Returns true if file is a supported format
    // */
    // public static boolean isSupportedFormat(File file)
    // {
    // return file.isFile() && isSupportedFormat(file.getName());
    // }
    //
    // /**
    // * Returns true if fileName is a supported format
    // */
    // public static boolean isSupportedFormat(String fileName)
    // {
    // fileName = fileName.toLowerCase(Locale.US);
    // for(int i = 0; i < SUPPORTED_FORMATS.length; i++)
    // {
    // if(fileName.endsWith(SUPPORTED_FORMATS[i]))
    // {
    // return true;
    // }
    // }
    // return false;
    // }

    public static byte[] uriTobuffer(final URI uri) throws IOException {
        return Resources.toByteArray(uri.toURL());
    }
}