Subversion Repositories SmartDukaan

Rev

Rev 37393 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
29337 amit.gupta 1
package com.smartdukaan.cron.scheduled;
2
 
30935 amit.gupta 3
import com.spice.profitmandi.common.web.client.RestClient;
30335 amit.gupta 4
import org.apache.commons.io.FileUtils;
5
import org.apache.logging.log4j.LogManager;
6
import org.apache.logging.log4j.Logger;
30935 amit.gupta 7
import org.springframework.beans.factory.annotation.Autowired;
30335 amit.gupta 8
import org.springframework.stereotype.Service;
29337 amit.gupta 9
 
37402 amit 10
import org.springframework.beans.factory.annotation.Value;
11
 
30335 amit.gupta 12
import java.io.File;
37402 amit 13
import java.io.OutputStream;
14
import java.net.HttpURLConnection;
15
import java.net.URL;
16
import java.nio.charset.StandardCharsets;
30335 amit.gupta 17
import java.util.Base64;
30935 amit.gupta 18
import java.util.HashMap;
37393 amit 19
import java.util.regex.Pattern;
30335 amit.gupta 20
 
29337 amit.gupta 21
@Service
22
public class CaptchaService {
23
 
30935 amit.gupta 24
	@Autowired
25
	RestClient restClient;
30335 amit.gupta 26
	private static final Logger LOGGER = LogManager.getLogger(CaptchaService.class);
29337 amit.gupta 27
 
37393 amit 28
	/** The 31 classes the solver model can emit: 123456789ABCDEFGHKMNPQRSTUVWXYZ */
29
	private static final Pattern CAPTCHA_CODE = Pattern.compile("^[1-9A-HK-NP-Z]{4}$");
30
 
37402 amit 31
	private static final String VERDICT_URL = "http://45.79.121.178/verdict";
32
 
33
	/** Falls back to a property when CAPTCHA_VERDICT_TOKEN is not in the environment. */
34
	@Value("${captcha.verdict.token:}")
35
	private String verdictTokenProperty;
36
 
37393 amit 37
	/**
38
	 * @return the 4-character captcha code, or null if the solver did not return
39
	 *         a usable one. Callers must skip the IMEI when this is null rather
40
	 *         than submitting the value to Vivo.
41
	 */
29337 amit.gupta 42
	public String getCaptchaCode(String filePath) throws Exception {
37402 amit 43
		return getCaptchaCode(FileUtils.readFileToByteArray(new File(filePath)));
44
	}
45
 
46
	public String getCaptchaCode(byte[] fileContent) throws Exception {
30935 amit.gupta 47
		String encodedString = Base64.getEncoder().encodeToString(fileContent);
48
		Base64Image base64Image = new Base64Image();
49
		base64Image.setImage(encodedString);
37393 amit 50
 
51
		String response = restClient.postJson("http://45.79.121.178/uploader", base64Image, new HashMap<>());
52
 
53
		// RestClient returns the body for ANY status, so an nginx 502 page or a
54
		// solver error payload arrives here looking like a normal result. Anything
55
		// that is not a real code is rejected instead of being sent to Vivo.
56
		if (response == null || !CAPTCHA_CODE.matcher(response.trim()).matches()) {
57
			LOGGER.error("Captcha solver returned an unusable response: {}",
58
					response == null ? "null" : response.substring(0, Math.min(response.length(), 200)));
59
			return null;
60
		}
61
		return response.trim();
29337 amit.gupta 62
	}
63
 
37402 amit 64
	/**
65
	 * Tell the solver whether Vivo accepted this code, so samples can be split
66
	 * into training data: accepted means the prediction was right (a free label),
67
	 * rejected means it was wrong (and needs a human to label it).
68
	 *
69
	 * Strictly best-effort. This is data collection - it must never slow down or
70
	 * break IMEI activation, so it has hard timeouts and swallows everything.
71
	 */
72
	public void reportVerdict(byte[] image, String code, boolean accepted) {
73
		String token = verdictToken();
74
		if (token == null || token.isEmpty()) {
75
			return;
76
		}
77
		HttpURLConnection conn = null;
78
		try {
79
			String body = "{\"image\":\"" + Base64.getEncoder().encodeToString(image)
80
					+ "\",\"code\":\"" + code + "\",\"accepted\":" + accepted + "}";
81
			conn = (HttpURLConnection) new URL(VERDICT_URL).openConnection();
82
			conn.setRequestMethod("POST");
83
			conn.setConnectTimeout(2000);
84
			conn.setReadTimeout(3000);
85
			conn.setDoOutput(true);
86
			conn.setRequestProperty("Content-Type", "application/json");
87
			conn.setRequestProperty("X-Verdict-Token", token);
88
			try (OutputStream os = conn.getOutputStream()) {
89
				os.write(body.getBytes(StandardCharsets.UTF_8));
90
			}
91
			int rc = conn.getResponseCode();
92
			if (rc != 200) {
93
				LOGGER.warn("verdict report returned HTTP {}", rc);
94
			}
95
		} catch (Exception e) {
96
			LOGGER.debug("verdict report failed (non-fatal)", e);
97
		} finally {
98
			if (conn != null) {
99
				conn.disconnect();
100
			}
101
		}
102
	}
103
 
104
	/** Environment wins, so the secret need not be committed to the properties file. */
105
	private String verdictToken() {
106
		String env = System.getenv("CAPTCHA_VERDICT_TOKEN");
107
		return (env != null && !env.isEmpty()) ? env : verdictTokenProperty;
108
	}
109
 
30935 amit.gupta 110
	class Base64Image {
111
		private String image;
29337 amit.gupta 112
 
30935 amit.gupta 113
		public String getImage() {
114
			return image;
115
		}
29337 amit.gupta 116
 
30935 amit.gupta 117
		public void setImage(String image) {
118
			this.image = image;
119
		}
120
	}
29337 amit.gupta 121
 
122
}