Index: trunk/src/oauth/signpost/AbstractOAuthConsumer.java
===================================================================
--- trunk/src/oauth/signpost/AbstractOAuthConsumer.java	(revision 6848)
+++ trunk/src/oauth/signpost/AbstractOAuthConsumer.java	(revision 6849)
@@ -34,5 +34,5 @@
  * ABC for consumer implementations. If you're developing a custom consumer you
  * will probably inherit from this class to save you a lot of work.
- * 
+ *
  * @author Matthias Kaeppler
  */
@@ -55,6 +55,8 @@
     // these are the params which will be passed to the message signer
     private HttpParameters requestParameters;
-
+    
     private boolean sendEmptyTokens;
+    
+    final private Random random = new Random(System.nanoTime());
 
     public AbstractOAuthConsumer(String consumerKey, String consumerSecret) {
@@ -78,5 +80,5 @@
     }
 
-    public HttpRequest sign(HttpRequest request) throws OAuthMessageSignerException,
+    public synchronized HttpRequest sign(HttpRequest request) throws OAuthMessageSignerException,
             OAuthExpectationFailedException, OAuthCommunicationException {
         if (consumerKey == null) {
@@ -109,5 +111,4 @@
 
         signingStrategy.writeSignature(signature, request, requestParameters);
-        OAuth.debugOut("Auth header", request.getHeader("Authorization"));
         OAuth.debugOut("Request URL", request.getRequestUrl());
 
@@ -115,10 +116,10 @@
     }
 
-    public HttpRequest sign(Object request) throws OAuthMessageSignerException,
+    public synchronized HttpRequest sign(Object request) throws OAuthMessageSignerException,
             OAuthExpectationFailedException, OAuthCommunicationException {
         return sign(wrap(request));
     }
 
-    public String sign(String url) throws OAuthMessageSignerException,
+    public synchronized String sign(String url) throws OAuthMessageSignerException,
             OAuthExpectationFailedException, OAuthCommunicationException {
         HttpRequest request = new UrlStringRequestAdapter(url);
@@ -139,5 +140,5 @@
      * Adapts the given request object to a Signpost {@link HttpRequest}. How
      * this is done depends on the consumer implementation.
-     * 
+     *
      * @param request
      *        the native HTTP request instance
@@ -179,5 +180,5 @@
      * {@link #generateNonce()} or {@link #generateTimestamp()} instead.
      * </p>
-     * 
+     *
      * @param out
      *        the request parameter which should be completed
@@ -257,5 +258,5 @@
 
     protected String generateNonce() {
-        return Long.toString(new Random().nextLong());
+        return Long.toString(random.nextLong());
     }
 }
Index: trunk/src/oauth/signpost/AbstractOAuthProvider.java
===================================================================
--- trunk/src/oauth/signpost/AbstractOAuthProvider.java	(revision 6848)
+++ trunk/src/oauth/signpost/AbstractOAuthProvider.java	(revision 6849)
@@ -58,7 +58,8 @@
     }
 
-    public String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl)
-            throws OAuthMessageSignerException, OAuthNotAuthorizedException,
-            OAuthExpectationFailedException, OAuthCommunicationException {
+    public synchronized String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl,
+            String... customOAuthParams) throws OAuthMessageSignerException,
+            OAuthNotAuthorizedException, OAuthExpectationFailedException,
+            OAuthCommunicationException {
 
         // invalidate current credentials, if any
@@ -67,5 +68,9 @@
         // 1.0a expects the callback to be sent while getting the request token.
         // 1.0 service providers would simply ignore this parameter.
-        retrieveToken(consumer, requestTokenEndpointUrl, OAuth.OAUTH_CALLBACK, callbackUrl);
+        HttpParameters params = new HttpParameters();
+        params.putAll(customOAuthParams, true);
+        params.put(OAuth.OAUTH_CALLBACK, callbackUrl, true);
+
+        retrieveToken(consumer, requestTokenEndpointUrl, params);
 
         String callbackConfirmed = responseParameters.getFirst(OAuth.OAUTH_CALLBACK_CONFIRMED);
@@ -84,7 +89,8 @@
     }
 
-    public void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier)
-            throws OAuthMessageSignerException, OAuthNotAuthorizedException,
-            OAuthExpectationFailedException, OAuthCommunicationException {
+    public synchronized void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier,
+            String... customOAuthParams) throws OAuthMessageSignerException,
+            OAuthNotAuthorizedException, OAuthExpectationFailedException,
+            OAuthCommunicationException {
 
         if (consumer.getToken() == null || consumer.getTokenSecret() == null) {
@@ -94,9 +100,11 @@
         }
 
+        HttpParameters params = new HttpParameters();
+        params.putAll(customOAuthParams, true);
+
         if (isOAuth10a && oauthVerifier != null) {
-            retrieveToken(consumer, accessTokenEndpointUrl, OAuth.OAUTH_VERIFIER, oauthVerifier);
-        } else {
-            retrieveToken(consumer, accessTokenEndpointUrl);
-        }
+            params.put(OAuth.OAUTH_VERIFIER, oauthVerifier, true);
+        }
+        retrieveToken(consumer, accessTokenEndpointUrl, params);
     }
 
@@ -126,10 +134,8 @@
      *        the URL at which the service provider serves the OAuth token that
      *        is to be fetched
-     * @param additionalParameters
-     *        you can pass parameters here (typically OAuth parameters such as
-     *        oauth_callback or oauth_verifier) which will go directly into the
-     *        signer, i.e. you don't have to put them into the request first,
-     *        just so the consumer pull them out again. Pass them sequentially
-     *        in key/value order.
+     * @param customOAuthParams
+     *        you can pass custom OAuth parameters here (such as oauth_callback
+     *        or oauth_verifier) which will go directly into the signer, i.e.
+     *        you don't have to put them into the request first.
      * @throws OAuthMessageSignerException
      *         if signing the token request fails
@@ -143,5 +149,5 @@
      */
     protected void retrieveToken(OAuthConsumer consumer, String endpointUrl,
-            String... additionalParameters) throws OAuthMessageSignerException,
+            HttpParameters customOAuthParams) throws OAuthMessageSignerException,
             OAuthCommunicationException, OAuthNotAuthorizedException,
             OAuthExpectationFailedException {
@@ -159,10 +165,8 @@
                 request.setHeader(header, defaultHeaders.get(header));
             }
-            if (additionalParameters != null) {
-                HttpParameters httpParams = new HttpParameters();
-                httpParams.putAll(additionalParameters, true);
-                consumer.setAdditionalParameters(httpParams);
-            }
-
+            if (customOAuthParams != null && !customOAuthParams.isEmpty()) {
+                consumer.setAdditionalParameters(customOAuthParams);
+            }
+            
             if (this.listener != null) {
                 this.listener.prepareRequest(request);
@@ -170,5 +174,5 @@
 
             consumer.sign(request);
-
+            
             if (this.listener != null) {
                 this.listener.prepareSubmission(request);
Index: trunk/src/oauth/signpost/OAuth.java
===================================================================
--- trunk/src/oauth/signpost/OAuth.java	(revision 6848)
+++ trunk/src/oauth/signpost/OAuth.java	(revision 6849)
@@ -239,4 +239,11 @@
     }
 
+    public static String addQueryString(String url, String queryString) {
+        String queryDelim = url.contains("?") ? "&" : "?";
+        StringBuilder sb = new StringBuilder(url + queryDelim);
+        sb.append(queryString);
+        return sb.toString();
+    }
+
     /**
      * Builds an OAuth header from the given list of header fields. All
@@ -250,5 +257,5 @@
      * 
      * <pre>
-     * OAuth realm="http://example.com", oauth_token="x%25y"
+     * OAuth realm=&quot;http://example.com&quot;, oauth_token=&quot;x%25y&quot;
      * </pre>
      * 
@@ -264,6 +271,7 @@
                 sb.append(", ");
             }
-            String value = kvPairs[i].startsWith("oauth_") ? OAuth
-                .percentEncode(kvPairs[i + 1]) : kvPairs[i + 1];
+            boolean isOAuthElem = kvPairs[i].startsWith("oauth_")
+                    || kvPairs[i].startsWith("x_oauth_");
+            String value = isOAuthElem ? OAuth.percentEncode(kvPairs[i + 1]) : kvPairs[i + 1];
             sb.append(OAuth.percentEncode(kvPairs[i]) + "=\"" + value + "\"");
         }
Index: trunk/src/oauth/signpost/OAuthConsumer.java
===================================================================
--- trunk/src/oauth/signpost/OAuthConsumer.java	(revision 6848)
+++ trunk/src/oauth/signpost/OAuthConsumer.java	(revision 6849)
@@ -74,7 +74,9 @@
      * i.e. you don't have to put them into the request first. The consumer's
      * {@link SigningStrategy} will then take care of writing them to the
-     * correct part of the request before it is sent. Note that these parameters
-     * are expected to already be percent encoded -- they will be simply merged
-     * as-is.
+     * correct part of the request before it is sent. This is useful if you want
+     * to pre-set custom OAuth parameters. Note that these parameters are
+     * expected to already be percent encoded -- they will be simply merged
+     * as-is. <b>BE CAREFUL WITH THIS METHOD! Your service provider may decide
+     * to ignore any non-standard OAuth params when computing the signature.</b>
      * 
      * @param additionalParameters
Index: trunk/src/oauth/signpost/OAuthProvider.java
===================================================================
--- trunk/src/oauth/signpost/OAuthProvider.java	(revision 6848)
+++ trunk/src/oauth/signpost/OAuthProvider.java	(revision 6849)
@@ -108,4 +108,9 @@
      *        your application as a desktop app (which would only be able to
      *        handle OOB requests).
+     * @param customOAuthParams
+     *        you can pass custom OAuth parameters here which will go directly
+     *        into the signer, i.e. you don't have to put them into the request
+     *        first. This is useful for pre-setting OAuth params for signing.
+     *        Pass them sequentially in key/value order.
      * @return The URL to which the user must be sent in order to authorize the
      *         consumer. It includes the unauthorized request token (and in the
@@ -122,7 +127,8 @@
      *         if server communication failed
      */
-    public String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl)
-            throws OAuthMessageSignerException, OAuthNotAuthorizedException,
-            OAuthExpectationFailedException, OAuthCommunicationException;
+    public String retrieveRequestToken(OAuthConsumer consumer, String callbackUrl,
+            String... customOAuthParams) throws OAuthMessageSignerException,
+            OAuthNotAuthorizedException, OAuthExpectationFailedException,
+            OAuthCommunicationException;
 
     /**
@@ -149,4 +155,9 @@
      *        value. If your app has received a callback, the verfication code
      *        was passed as part of that request instead.
+     * @param customOAuthParams
+     *        you can pass custom OAuth parameters here which will go directly
+     *        into the signer, i.e. you don't have to put them into the request
+     *        first. This is useful for pre-setting OAuth params for signing.
+     *        Pass them sequentially in key/value order.
      * @throws OAuthMessageSignerException
      *         if signing the request failed
@@ -159,7 +170,8 @@
      *         if server communication failed
      */
-    public void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier)
-            throws OAuthMessageSignerException, OAuthNotAuthorizedException,
-            OAuthExpectationFailedException, OAuthCommunicationException;
+    public void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier,
+            String... customOAuthParams) throws OAuthMessageSignerException,
+            OAuthNotAuthorizedException, OAuthExpectationFailedException,
+            OAuthCommunicationException;
 
     /**
Index: trunk/src/oauth/signpost/basic/HttpURLConnectionResponseAdapter.java
===================================================================
--- trunk/src/oauth/signpost/basic/HttpURLConnectionResponseAdapter.java	(revision 6848)
+++ trunk/src/oauth/signpost/basic/HttpURLConnectionResponseAdapter.java	(revision 6849)
@@ -16,5 +16,9 @@
 
     public InputStream getContent() throws IOException {
-        return connection.getInputStream();
+        try {
+            return connection.getInputStream();
+        } catch (IOException e) {
+            return connection.getErrorStream();
+        }
     }
 
Index: trunk/src/oauth/signpost/http/HttpParameters.java
===================================================================
--- trunk/src/oauth/signpost/http/HttpParameters.java	(revision 6848)
+++ trunk/src/oauth/signpost/http/HttpParameters.java	(revision 6849)
@@ -87,16 +87,18 @@
      */
     public String put(String key, String value, boolean percentEncode) {
-        SortedSet<String> values = wrappedMap.get(key);
-        if (values == null) {
-            values = new TreeSet<String>();
-            wrappedMap.put(percentEncode ? OAuth.percentEncode(key) : key, values);
-        }
-        if (value != null) {
-            value = percentEncode ? OAuth.percentEncode(value) : value;
-            values.add(value);
-        }
-
-        return value;
-    }
+         // fix contributed by Bjorn Roche - key should be encoded before wrappedMap.get
+         key = percentEncode ? OAuth.percentEncode(key) : key;
+         SortedSet<String> values = wrappedMap.get(key);
+         if (values == null) {
+             values = new TreeSet<String>();
+             wrappedMap.put( key, values);
+         }
+         if (value != null) {
+             value = percentEncode ? OAuth.percentEncode(value) : value;
+             values.add(value);
+         }
+
+         return value;
+     }
 
     /**
@@ -200,6 +202,26 @@
      */
     public String getAsQueryString(Object key) {
+    	return getAsQueryString(key, true);
+    }
+
+    /**
+     * Concatenates all values for the given key to a list of key/value pairs
+     * suitable for use in a URL query string.
+     * 
+     * @param key
+     *        the parameter name
+     * @param percentEncode
+     *        whether key should be percent encoded before being
+     *        used with the map
+     * @return the query string
+     */
+     public String getAsQueryString(Object key, boolean percentEncode) {
+        // fix contributed by Stjepan Rajko - we need the percentEncode parameter
+        // because some places (like SignatureBaseString.normalizeRequestParameters)
+        // need to supply the parameter percent encoded
+
         StringBuilder sb = new StringBuilder();
-        key = OAuth.percentEncode((String) key);
+        if(percentEncode)
+        	key = OAuth.percentEncode((String) key);
         Set<String> values = wrappedMap.get(key);
         if (values == null) {
@@ -215,5 +237,5 @@
         return sb.toString();
     }
-
+    
     public String getAsHeaderElement(String key) {
         String value = getFirst(key);
@@ -265,6 +287,19 @@
     }
 
-    public Set<java.util.Map.Entry<String, SortedSet<String>>> entrySet() {
+    public Set<Entry<String, SortedSet<String>>> entrySet() {
         return wrappedMap.entrySet();
     }
+
+    public HttpParameters getOAuthParameters() {
+        HttpParameters oauthParams = new HttpParameters();
+
+        for (Entry<String, SortedSet<String>> param : this.entrySet()) {
+            String key = param.getKey();
+            if (key.startsWith("oauth_") || key.startsWith("x_oauth_")) {
+                oauthParams.put(key, param.getValue());
+            }
+        }
+
+        return oauthParams;
+    }
 }
Index: trunk/src/oauth/signpost/signature/AuthorizationHeaderSigningStrategy.java
===================================================================
--- trunk/src/oauth/signpost/signature/AuthorizationHeaderSigningStrategy.java	(revision 6848)
+++ trunk/src/oauth/signpost/signature/AuthorizationHeaderSigningStrategy.java	(revision 6849)
@@ -1,3 +1,5 @@
 package oauth.signpost.signature;
+
+import java.util.Iterator;
 
 import oauth.signpost.OAuth;
@@ -19,33 +21,26 @@
 
         sb.append("OAuth ");
+
+        // add the realm parameter, if any
         if (requestParameters.containsKey("realm")) {
             sb.append(requestParameters.getAsHeaderElement("realm"));
             sb.append(", ");
         }
-        if (requestParameters.containsKey(OAuth.OAUTH_TOKEN)) {
-            sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_TOKEN));
-            sb.append(", ");
+
+        // add all (x_)oauth parameters
+        HttpParameters oauthParams = requestParameters.getOAuthParameters();
+        oauthParams.put(OAuth.OAUTH_SIGNATURE, signature, true);
+
+        Iterator<String> iter = oauthParams.keySet().iterator();
+        while (iter.hasNext()) {
+            String key = iter.next();
+            sb.append(oauthParams.getAsHeaderElement(key));
+            if (iter.hasNext()) {
+                sb.append(", ");
+            }
         }
-        if (requestParameters.containsKey(OAuth.OAUTH_CALLBACK)) {
-            sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_CALLBACK));
-            sb.append(", ");
-        }
-        if (requestParameters.containsKey(OAuth.OAUTH_VERIFIER)) {
-            sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_VERIFIER));
-            sb.append(", ");
-        }
-        sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_CONSUMER_KEY));
-        sb.append(", ");
-        sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_VERSION));
-        sb.append(", ");
-        sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_SIGNATURE_METHOD));
-        sb.append(", ");
-        sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_TIMESTAMP));
-        sb.append(", ");
-        sb.append(requestParameters.getAsHeaderElement(OAuth.OAUTH_NONCE));
-        sb.append(", ");
-        sb.append(OAuth.toHeaderElement(OAuth.OAUTH_SIGNATURE, signature));
 
         String header = sb.toString();
+        OAuth.debugOut("Auth Header", header);
         request.setHeader(OAuth.HTTP_AUTHORIZATION_HEADER, header);
 
Index: trunk/src/oauth/signpost/signature/QueryStringSigningStrategy.java
===================================================================
--- trunk/src/oauth/signpost/signature/QueryStringSigningStrategy.java	(revision 6848)
+++ trunk/src/oauth/signpost/signature/QueryStringSigningStrategy.java	(revision 6849)
@@ -1,3 +1,5 @@
 package oauth.signpost.signature;
+
+import java.util.Iterator;
 
 import oauth.signpost.OAuth;
@@ -21,33 +23,20 @@
             HttpParameters requestParameters) {
 
-        // add the signature
-        StringBuilder sb = new StringBuilder(OAuth.addQueryParameters(request.getRequestUrl(),
-            OAuth.OAUTH_SIGNATURE, signature));
+        // add all (x_)oauth parameters
+        HttpParameters oauthParams = requestParameters.getOAuthParameters();
+        oauthParams.put(OAuth.OAUTH_SIGNATURE, signature, true);
 
-        // add the optional OAuth parameters
-        if (requestParameters.containsKey(OAuth.OAUTH_TOKEN)) {
+        Iterator<String> iter = oauthParams.keySet().iterator();
+
+        // add the first query parameter (we always have at least the signature)
+        String firstKey = iter.next();
+        StringBuilder sb = new StringBuilder(OAuth.addQueryString(request.getRequestUrl(),
+            oauthParams.getAsQueryString(firstKey)));
+
+        while (iter.hasNext()) {
             sb.append("&");
-            sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_TOKEN));
+            String key = iter.next();
+            sb.append(oauthParams.getAsQueryString(key));
         }
-        if (requestParameters.containsKey(OAuth.OAUTH_CALLBACK)) {
-            sb.append("&");
-            sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_CALLBACK));
-        }
-        if (requestParameters.containsKey(OAuth.OAUTH_VERIFIER)) {
-            sb.append("&");
-            sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_VERIFIER));
-        }
-
-        // add the remaining OAuth params
-        sb.append("&");
-        sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_CONSUMER_KEY));
-        sb.append("&");
-        sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_VERSION));
-        sb.append("&");
-        sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_SIGNATURE_METHOD));
-        sb.append("&");
-        sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_TIMESTAMP));
-        sb.append("&");
-        sb.append(requestParameters.getAsQueryString(OAuth.OAUTH_NONCE));
 
         String signedUrl = sb.toString();
Index: trunk/src/oauth/signpost/signature/SignatureBaseString.java
===================================================================
--- trunk/src/oauth/signpost/signature/SignatureBaseString.java	(revision 6848)
+++ trunk/src/oauth/signpost/signature/SignatureBaseString.java	(revision 6849)
@@ -111,5 +111,7 @@
             }
 
-            sb.append(requestParameters.getAsQueryString(param));
+            // fix contributed by Stjepan Rajko
+            // since param should already be encoded, we supply false for percentEncode
+            sb.append(requestParameters.getAsQueryString(param, false));  
         }
         return sb.toString();
