@@ -71,6 +71,8 @@ static struct curl_slist *no_pragma_header;
7171
7272static struct active_request_slot * active_queue_head ;
7373
74+ static char * cached_accept_language ;
75+
7476size_t fread_buffer (char * ptr , size_t eltsize , size_t nmemb , void * buffer_ )
7577{
7678 size_t size = eltsize * nmemb ;
@@ -549,6 +551,9 @@ void http_cleanup(void)
549551 cert_auth .password = NULL ;
550552 }
551553 ssl_cert_password_required = 0 ;
554+
555+ free (cached_accept_language );
556+ cached_accept_language = NULL ;
552557}
553558
554559struct active_request_slot * get_active_slot (void )
@@ -996,6 +1001,142 @@ static void extract_content_type(struct strbuf *raw, struct strbuf *type,
9961001}
9971002
9981003
1004+ /*
1005+ * Guess the user's preferred languages from the value in LANGUAGE environment
1006+ * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined.
1007+ *
1008+ * The result can be a colon-separated list like "ko:ja:en".
1009+ */
1010+ static const char * get_preferred_languages (void )
1011+ {
1012+ const char * retval ;
1013+
1014+ retval = getenv ("LANGUAGE" );
1015+ if (retval && * retval )
1016+ return retval ;
1017+
1018+ #ifndef NO_GETTEXT
1019+ retval = setlocale (LC_MESSAGES , NULL );
1020+ if (retval && * retval &&
1021+ strcmp (retval , "C" ) &&
1022+ strcmp (retval , "POSIX" ))
1023+ return retval ;
1024+ #endif
1025+
1026+ return NULL ;
1027+ }
1028+
1029+ static void write_accept_language (struct strbuf * buf )
1030+ {
1031+ /*
1032+ * MAX_DECIMAL_PLACES must not be larger than 3. If it is larger than
1033+ * that, q-value will be smaller than 0.001, the minimum q-value the
1034+ * HTTP specification allows. See
1035+ * http://tools.ietf.org/html/rfc7231#section-5.3.1 for q-value.
1036+ */
1037+ const int MAX_DECIMAL_PLACES = 3 ;
1038+ const int MAX_LANGUAGE_TAGS = 1000 ;
1039+ const int MAX_ACCEPT_LANGUAGE_HEADER_SIZE = 4000 ;
1040+ char * * language_tags = NULL ;
1041+ int num_langs = 0 ;
1042+ const char * s = get_preferred_languages ();
1043+ int i ;
1044+ struct strbuf tag = STRBUF_INIT ;
1045+
1046+ /* Don't add Accept-Language header if no language is preferred. */
1047+ if (!s )
1048+ return ;
1049+
1050+ /*
1051+ * Split the colon-separated string of preferred languages into
1052+ * language_tags array.
1053+ */
1054+ do {
1055+ /* collect language tag */
1056+ for (; * s && (isalnum (* s ) || * s == '_' ); s ++ )
1057+ strbuf_addch (& tag , * s == '_' ? '-' : * s );
1058+
1059+ /* skip .codeset, @modifier and any other unnecessary parts */
1060+ while (* s && * s != ':' )
1061+ s ++ ;
1062+
1063+ if (tag .len ) {
1064+ num_langs ++ ;
1065+ REALLOC_ARRAY (language_tags , num_langs );
1066+ language_tags [num_langs - 1 ] = strbuf_detach (& tag , NULL );
1067+ if (num_langs >= MAX_LANGUAGE_TAGS - 1 ) /* -1 for '*' */
1068+ break ;
1069+ }
1070+ } while (* s ++ );
1071+
1072+ /* write Accept-Language header into buf */
1073+ if (num_langs ) {
1074+ int last_buf_len = 0 ;
1075+ int max_q ;
1076+ int decimal_places ;
1077+ char q_format [32 ];
1078+
1079+ /* add '*' */
1080+ REALLOC_ARRAY (language_tags , num_langs + 1 );
1081+ language_tags [num_langs ++ ] = "*" ; /* it's OK; this won't be freed */
1082+
1083+ /* compute decimal_places */
1084+ for (max_q = 1 , decimal_places = 0 ;
1085+ max_q < num_langs && decimal_places <= MAX_DECIMAL_PLACES ;
1086+ decimal_places ++ , max_q *= 10 )
1087+ ;
1088+
1089+ sprintf (q_format , ";q=0.%%0%dd" , decimal_places );
1090+
1091+ strbuf_addstr (buf , "Accept-Language: " );
1092+
1093+ for (i = 0 ; i < num_langs ; i ++ ) {
1094+ if (i > 0 )
1095+ strbuf_addstr (buf , ", " );
1096+
1097+ strbuf_addstr (buf , language_tags [i ]);
1098+
1099+ if (i > 0 )
1100+ strbuf_addf (buf , q_format , max_q - i );
1101+
1102+ if (buf -> len > MAX_ACCEPT_LANGUAGE_HEADER_SIZE ) {
1103+ strbuf_remove (buf , last_buf_len , buf -> len - last_buf_len );
1104+ break ;
1105+ }
1106+
1107+ last_buf_len = buf -> len ;
1108+ }
1109+ }
1110+
1111+ /* free language tags -- last one is a static '*' */
1112+ for (i = 0 ; i < num_langs - 1 ; i ++ )
1113+ free (language_tags [i ]);
1114+ free (language_tags );
1115+ }
1116+
1117+ /*
1118+ * Get an Accept-Language header which indicates user's preferred languages.
1119+ *
1120+ * Examples:
1121+ * LANGUAGE= -> ""
1122+ * LANGUAGE=ko:en -> "Accept-Language: ko, en; q=0.9, *; q=0.1"
1123+ * LANGUAGE=ko_KR.UTF-8:sr@latin -> "Accept-Language: ko-KR, sr; q=0.9, *; q=0.1"
1124+ * LANGUAGE=ko LANG=en_US.UTF-8 -> "Accept-Language: ko, *; q=0.1"
1125+ * LANGUAGE= LANG=en_US.UTF-8 -> "Accept-Language: en-US, *; q=0.1"
1126+ * LANGUAGE= LANG=C -> ""
1127+ */
1128+ static const char * get_accept_language (void )
1129+ {
1130+ if (!cached_accept_language ) {
1131+ struct strbuf buf = STRBUF_INIT ;
1132+ write_accept_language (& buf );
1133+ if (buf .len > 0 )
1134+ cached_accept_language = strbuf_detach (& buf , NULL );
1135+ }
1136+
1137+ return cached_accept_language ;
1138+ }
1139+
9991140/* http_request() targets */
10001141#define HTTP_REQUEST_STRBUF 0
10011142#define HTTP_REQUEST_FILE 1
@@ -1008,6 +1149,7 @@ static int http_request(const char *url,
10081149 struct slot_results results ;
10091150 struct curl_slist * headers = NULL ;
10101151 struct strbuf buf = STRBUF_INIT ;
1152+ const char * accept_language ;
10111153 int ret ;
10121154
10131155 slot = get_active_slot ();
@@ -1033,6 +1175,11 @@ static int http_request(const char *url,
10331175 fwrite_buffer );
10341176 }
10351177
1178+ accept_language = get_accept_language ();
1179+
1180+ if (accept_language )
1181+ headers = curl_slist_append (headers , accept_language );
1182+
10361183 strbuf_addstr (& buf , "Pragma:" );
10371184 if (options && options -> no_cache )
10381185 strbuf_addstr (& buf , " no-cache" );
0 commit comments