1

For the past few days I have been trying to automatically upload a Minecraft skin to Mojang's servers. I am able to successfully log into my skin account (and set the cookies appropriately). Without setting the cookies, when I go to https://minecraft.net/profile I get sent to the login page, but if I do set the cookies it brings me to the profile page as it should. I have looked through the POST data being sent many times when I upload a skin, but for the life of me I couldn't get it to work. I have tried many people's fixes, but yet I cannot find a fix that works.

public static void uploadSkin(BufferedImage image, boolean male, String username, String password){
    try {
        URL url = new URL("https://minecraft.net/login");
        URLConnection con = url.openConnection();
        HttpURLConnection http = (HttpURLConnection) con;
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        Map<String, String> arguments = new HashMap<>();
        arguments.put("username", username);
        arguments.put("password", password);
        String s = "";
        for(Map.Entry<String, String> entry : arguments.entrySet())s += "&" + URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
        s = s.replaceFirst("&", "");
        byte[] out = s.getBytes(StandardCharsets.UTF_8);
        int length = out.length;
        http.setFixedLengthStreamingMode(length);
        http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        http.setInstanceFollowRedirects(false);
        http.connect();
        OutputStream os = http.getOutputStream();
        os.write(out);
        String cooks = "";
        String at = "";
        for(int i = 0; i < 50; i++){
            String headerName = http.getHeaderFieldKey(i);
            String headerValue = http.getHeaderField(i);
            if(headerName != null && headerValue != null)if("Set-Cookie".equalsIgnoreCase(headerName))cooks += ";" + headerValue.split(";")[0];
        }
        http.disconnect();
        URL url3 = new URL("https://minecraft.net/profile");
        URLConnection con3 = url3.openConnection();
        HttpURLConnection http3 = (HttpURLConnection) con3;
        http3.setRequestProperty("Cookie", cooks);
        http3.connect();
        for(int i = 0; i < 50; i++){
            String headerName = http3.getHeaderFieldKey(i);
            String headerValue = http3.getHeaderField(i);
            if(headerName != null && headerValue != null)if("Set-Cookie".equalsIgnoreCase(headerName))if(headerValue.startsWith("PLAY_SESSION"))at = headerValue.split("AT=")[1].split("\"")[0];
        }
        http3.disconnect();
        cooks = cooks.replaceFirst(";", "");
        URL url2 = new URL("https://minecraft.net/profile/skin");
        URLConnection con2 = url2.openConnection();
        HttpURLConnection http2 = (HttpURLConnection) con2;
        http2.setRequestProperty("Cookie", cooks);
        http2.setRequestMethod("POST");
        http2.setDoOutput(true);
        Map<String, String> arguments2 = new HashMap<>();
        arguments2.put("model", male ? "steve" : "3pxarm");
        arguments2.put("authenticityToken", at);
        String encoded = "PNG";
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", bos);
        byte[] imageBytes = bos.toByteArray();
        BASE64Encoder encoder = new BASE64Encoder();
        encoded += encoder.encode(imageBytes);
        bos.close();
        arguments2.put("skin", encoded);
        String s2 = "";
        for(Map.Entry<String, String> entry : arguments2.entrySet())s2 += "&" + URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
        s2 = s2.replaceFirst("&", "");
        byte[] out2 = s2.getBytes(StandardCharsets.UTF_8);
        int length2 = out2.length;
        http2.setFixedLengthStreamingMode(length2);
        http2.setRequestProperty("Content-Type", "multipart/form-data; charset=UTF-8;");
        http2.setInstanceFollowRedirects(false);
        http2.connect();
        OutputStream os2 = http2.getOutputStream();
        os2.write(out2);
        InputStream is = http2.getInputStream();
        Scanner sc = new Scanner(is, "UTF-8");
        sc.useDelimiter("\\A");
        while(sc.hasNext())System.out.println(sc.next());
        sc.close();
        http2.disconnect();
    } catch (Exception e) {e.printStackTrace();}
}

I get this error when trying to run it

java.io.IOException: Server returned HTTP response code: 500 for URL: https://minecraft.net/profile/skin
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)
at net.supernatenetwork.snn.TTESTT.uploadSkin(TTESTT.java:104)
at net.supernatenetwork.snn.TTESTT.main(TTESTT.java:27)

Line 98 is "InputStream is = http2.getInputStream();" When I put the block for reading the HTML with the first http variable, it prints out nothing. I keep getting the error code 404. I do know that the error code 404 translates to file not found, but if I send no POST data it brings me to the profile page, so I am assuming it is linked to POST, since if I remove one of the fields in the first http, it gives me the same error (just with the login page and only if I try to get the data from that). The BufferedImage is not null, and it has data. I need it to be a BufferedImage because I need to edit it from a template before uploading it. After looking, I see that the content type for the skin is image/png, but I need it to be what I currently have it as there is multiple things going into the POST. My encoded variable starts with "PNG" because when I was debugging in Firefox, I saw that the skin starts with PNG (might just be a coincidence). I have tried it without the "PNG" but still had no luck. The image I am uploading is a PNG. Any help is appreciated! Thanks!

Edit: I got a different error (from the new code) which I put both where the old code and error was. I left the old notes untouched. Credit to gre_gor for helping me find out a few of the errors.

Edit 2: I know how to send things through post. I was just wondering how to upload a skin, which is different from what this was marked for being a duplicate.

ACCFfan
  • 50
  • 7
  • Why are you encoding the image into base64? `PNG` is part of the PNG file header. `authenticityToken` is not the same as `access_token` in cookies. Skin is uploaded to `https://minecraft.net/profile/skin` and the data needs to be encoded into `multipart/form-data` not `application/x-www-form-urlencoded`. You should use a HTTP library to handle/parse those things. – gre_gor Apr 05 '16 at 01:15
  • @gre_gor Thank you for responding so quickly. I fixed most of the errors you listed, and it brought me to a different error (I updated the code and error above). I am assuming the reason I got `response code 500` was because the image wasn't uploaded right, which was the only error you listed that I could not fix. I do not want to use external libraries. I am confused on what you want me to do. Are you suggesting that I upload the `byte[]` of the image as I did with `out2` through `os2`? And if I would, how would I go about doing that while still having the value assigned to `skin`? – ACCFfan Apr 05 '16 at 14:34
  • Possible duplicate of [Sending HTTP POST Request In Java](http://stackoverflow.com/questions/3324717/sending-http-post-request-in-java) – Ferrybig Apr 12 '16 at 13:35
  • @Ferrybig this is not a duplicate of that question. – ACCFfan Apr 13 '16 at 00:04

1 Answers1

1

To upload files you, need to encode the data as multipart/form-data. Just changing Content-Type to multipart/form-data won't magically make it work. You need to construct proper HTTP payload yourself.

It should look something like that:

------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="authenticityToken"

78142fca85887e53d795fab390a78cfe1f96acd5
------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="model"

steve
------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="skin"; filename="skin.png"
Content-Type: image/png

[PNG skin image data]
------WebKitFormBoundary4ytVzCJnzOYoi3v4--

Here is the working code:

import java.util.*;
import java.lang.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.net.HttpCookie;
import java.net.CookieManager;
import java.net.CookieHandler;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;

class SkinUpload
{
    public static void login(String email, String password) throws java.lang.Exception
    {
        String payload = "username=" + URLEncoder.encode(email, "UTF-8");
        payload += "&password=" + URLEncoder.encode(password, "UTF-8");
        byte[] payload_data = payload.getBytes("UTF-8");

        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/login").openConnection());
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        http.setFixedLengthStreamingMode(payload_data.length);

        http.connect();
        http.getOutputStream().write(payload_data);

        System.out.println("login: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static void profile() throws java.lang.Exception
    {
        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/profile").openConnection());
        http.setRequestMethod("GET");

        http.connect();

        System.out.println("profile: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static void upload(String authenticityToken, BufferedImage image, boolean male) throws java.lang.Exception
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);

        String boundary = Long.toHexString(System.currentTimeMillis()); 

        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/profile/skin").openConnection());
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

        http.connect();

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"authenticityToken\"\r\n\r\n").getBytes());
        http.getOutputStream().write((authenticityToken+"\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"model\"\r\n\r\n").getBytes());
        http.getOutputStream().write(((male ? "steve" : "3pxarm")+"\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"skin\"; filename=\"skin.png\"\r\n").getBytes());
        http.getOutputStream().write(("Content-Type: image/png\r\n\r\n").getBytes());
        http.getOutputStream().write(baos.toByteArray());
        http.getOutputStream().write(("\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"--\r\n").getBytes());

        http.getOutputStream().flush();

        System.out.println("upload: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static String get_authenticityToken(List<HttpCookie> cookies) throws java.lang.Exception
    {
        for (HttpCookie cookie : cookies)
        {
            if (cookie.getName().equals("PLAY_SESSION"))
            {
                for (String param : cookie.getValue().split("&"))
                {
                    int i = param.indexOf("=");
                    String name = URLDecoder.decode(param.substring(0, i), "UTF-8");
                    String value = URLDecoder.decode(param.substring(i + 1), "UTF-8");
                    if (name.equals("___AT"))
                        return value;
                }
            }
        }
        return null;
    }
    public static void main (String[] args) throws java.lang.Exception
    {
        if (args.length < 3)
        {
            System.out.println("Usage:\nSkinUpload [email] [password] [image file path]");
            return;
        }
        String email = args[0];
        String password = args[1];
        String image_file = args[2];

        // this should handle cookies for all HTTP requests
        CookieManager cookieManager = new CookieManager();
        CookieHandler.setDefault(cookieManager);

        login(email, password);
        profile();

        // get authenticityToken  from picked up cookies
        String authenticityToken = get_authenticityToken(cookieManager.getCookieStore().getCookies());
        if (authenticityToken == null)
        {
            System.out.println("Failed to get authenticityToken");
        }
        else
        {
            System.out.println("authenticityToken = "+authenticityToken);

            BufferedImage skin = ImageIO.read(new File(image_file));

            upload(authenticityToken, skin, true);
        }
    }
}
gre_gor
  • 6,669
  • 9
  • 47
  • 52