[shotwell: 1/2] Port to libsoup3
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell: 1/2] Port to libsoup3
- Date: Mon, 18 Jul 2022 20:42:09 +0000 (UTC)
commit 285c9674cf4691a29bfa468b935418ab065d85b1
Author: Jens Georg <mail jensge org>
Date: Mon Jul 18 20:42:08 2022 +0000
Port to libsoup3
meson.build | 6 +-
.../shotwell/FlickrPublishingAuthenticator.vala | 24 +-
.../shotwell/GoogleAuthenticator.vala | 31 +-
plugins/common/OAuth1Support.vala | 9 +-
plugins/common/RESTSupport.vala | 164 +-
plugins/common/WebAuthenticationPane.vala | 1 -
.../RajcePublishing.vala | 1597 --------------------
.../YandexPublishing.vala | 662 --------
plugins/shotwell-publishing-extras/meson.build | 2 -
....gnome.Shotwell.Publishing.Extras.gresource.xml | 4 -
plugins/shotwell-publishing-extras/rajce.png | Bin 1650 -> 0 bytes
.../rajce_authentication_pane.ui | 142 --
.../rajce_publishing_options_pane.ui | 246 ---
.../shotwell-publishing-extras.vala | 7 -
.../yandex_publish_model.ui | 182 ---
plugins/shotwell-publishing/FlickrPublishing.vala | 2 +-
plugins/shotwell-publishing/PhotosUploader.vala | 22 +-
plugins/shotwell-publishing/PiwigoPublishing.vala | 15 +-
plugins/shotwell-publishing/TumblrPublishing.vala | 6 +-
plugins/shotwell-publishing/YouTubePublishing.vala | 143 +-
plugins/shotwell-publishing/YoutubeUploader.vala | 133 ++
plugins/shotwell-publishing/meson.build | 3 +-
test/server.py | 68 +-
23 files changed, 383 insertions(+), 3086 deletions(-)
---
diff --git a/meson.build b/meson.build
index 0fcaf521..5e0d1177 100644
--- a/meson.build
+++ b/meson.build
@@ -49,8 +49,8 @@ gio = dependency('gio-2.0', version: '>= 2.40')
gmodule = dependency('gmodule-2.0', version: '>= 2.40')
gio_unix = dependency('gio-unix-2.0', version: '>= 2.40')
gee = dependency('gee-0.8', version: '>= 0.8.5')
-webkit = dependency('webkit2gtk-4.0', version: '>= 2.26')
-soup = dependency('libsoup-2.4')
+webkit = dependency('webkit2gtk-4.1', version: '>= 2.26')
+soup = dependency('libsoup-3.0')
json_glib = dependency('json-glib-1.0')
xml = dependency('libxml-2.0')
gdk = dependency('gdk-3.0', version : '>= 3.22')
@@ -110,7 +110,7 @@ if get_option('face_detection')
endif
json_glib = dependency('json-glib-1.0')
-gdata = dependency('libgdata')
+# gdata = dependency('libgdata')
gcr = dependency('gcr-3')
gcr_ui = dependency('gcr-ui-3')
cairo = dependency('cairo')
diff --git a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
index 22b19aa4..a6d84305 100644
--- a/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
+++ b/plugins/authenticator/shotwell/FlickrPublishingAuthenticator.vala
@@ -64,10 +64,16 @@ namespace Publishing.Authenticator.Shotwell.Flickr {
return;
}
- var uri = new Soup.URI(get_view().get_uri());
- if (uri.scheme == "shotwell-auth" && this.auth_code == null) {
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("oauth_verifier");
+ try {
+ var uri = GLib.Uri.parse(get_view().get_uri(), GLib.UriFlags.NONE);
+ if (uri.get_scheme() == "shotwell-auth" && this.auth_code == null) {
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("oauth_verifier");
+ }
+ } catch (Error err) {
+ this.error();
+
+ return;
}
if (this.auth_code != null) {
@@ -76,9 +82,13 @@ namespace Publishing.Authenticator.Shotwell.Flickr {
}
private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) {
- var uri = new Soup.URI(request.get_uri());
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("oauth_verifier");
+ try {
+ var uri = GLib.Uri.parse(request.get_uri(), GLib.UriFlags.NONE);
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("oauth_verifier");
+ } catch (Error err) {
+ debug ("Failed to parse URI %s: %s", request.get_uri(), err.message);
+ }
var response = "";
var mins = new MemoryInputStream.from_data(response.data, null);
diff --git a/plugins/authenticator/shotwell/GoogleAuthenticator.vala
b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
index a0a0bf99..61e763cc 100644
--- a/plugins/authenticator/shotwell/GoogleAuthenticator.vala
+++ b/plugins/authenticator/shotwell/GoogleAuthenticator.vala
@@ -30,10 +30,15 @@ namespace Publishing.Authenticator.Shotwell.Google {
return;
}
- var uri = new Soup.URI(get_view().get_uri());
- if (uri.scheme == REVERSE_CLIENT_ID && this.auth_code == null) {
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("code");
+ try {
+ var uri = GLib.Uri.parse(get_view().get_uri(), UriFlags.NONE);
+ if (uri.get_scheme() == REVERSE_CLIENT_ID && this.auth_code == null) {
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("code");
+ }
+ } catch (Error err) {
+ debug ("Failed to parse auth code from URI %s: %s", get_view().get_uri(),
+ err.message);
}
if (this.auth_code != null) {
@@ -42,10 +47,14 @@ namespace Publishing.Authenticator.Shotwell.Google {
}
private void on_shotwell_auth_request_cb(WebKit.URISchemeRequest request) {
- var uri = new Soup.URI(request.get_uri());
- debug("URI: %s", request.get_uri());
- var form_data = Soup.Form.decode (uri.query);
- this.auth_code = form_data.lookup("code");
+ try {
+ var uri = GLib.Uri.parse(request.get_uri(), GLib.UriFlags.NONE);
+ debug("URI: %s", request.get_uri());
+ var form_data = Soup.Form.decode (uri.get_query());
+ this.auth_code = form_data.lookup("code");
+ } catch (Error err) {
+ debug("Failed to parse request URI: %s", err.message);
+ }
var response = "";
var mins = new MemoryInputStream.from_data(response.data, null);
@@ -200,9 +209,9 @@ namespace Publishing.Authenticator.Shotwell.Google {
string user_authorization_url = "https://accounts.google.com/o/oauth2/auth?" +
"response_type=code&" +
"client_id=" + OAUTH_CLIENT_ID + "&" +
- "redirect_uri=" + Soup.URI.encode(OAUTH_CALLBACK_URI, null) + "&" +
- "scope=" + Soup.URI.encode(this.scope, null) + "+" +
- Soup.URI.encode("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
+ "redirect_uri=" + GLib.Uri.escape_string(OAUTH_CALLBACK_URI, null) + "&" +
+ "scope=" + GLib.Uri.escape_string(this.scope, null) + "+" +
+ GLib.Uri.escape_string("https://www.googleapis.com/auth/userinfo.profile", null) + "&" +
"state=connect&" +
"access_type=offline&" +
"approval_prompt=force";
diff --git a/plugins/common/OAuth1Support.vala b/plugins/common/OAuth1Support.vala
index e94f5fa3..e5b77606 100644
--- a/plugins/common/OAuth1Support.vala
+++ b/plugins/common/OAuth1Support.vala
@@ -6,7 +6,6 @@
*/
namespace Publishing.RESTSupport.OAuth1 {
- internal const string ENCODE_RFC_3986_EXTRA = "!*'();:@&=+$,/?%#[] \\";
public class Session : Publishing.RESTSupport.Session {
private string? request_phase_token = null;
@@ -80,9 +79,9 @@ namespace Publishing.RESTSupport.OAuth1 {
signing_key = consumer_secret + "&";
}
- string signature_base_string = http_method + "&" + Soup.URI.encode(
- txn.get_endpoint_url(), ENCODE_RFC_3986_EXTRA) + "&" +
- Soup.URI.encode(arguments_string, ENCODE_RFC_3986_EXTRA);
+ string signature_base_string = http_method + "&" + GLib.Uri.escape_string(
+ txn.get_endpoint_url()) + "&" +
+ GLib.Uri.escape_string (arguments_string);
debug("signature base string = '%s'", signature_base_string);
@@ -90,7 +89,7 @@ namespace Publishing.RESTSupport.OAuth1 {
// compute the signature
string signature = RESTSupport.hmac_sha1(signing_key, signature_base_string);
- signature = Soup.URI.encode(signature, ENCODE_RFC_3986_EXTRA);
+ signature = GLib.Uri.escape_string(signature);
debug("signature = '%s'", signature);
diff --git a/plugins/common/RESTSupport.vala b/plugins/common/RESTSupport.vala
index cb050189..cddedf4a 100644
--- a/plugins/common/RESTSupport.vala
+++ b/plugins/common/RESTSupport.vala
@@ -26,6 +26,9 @@ public abstract class Session {
private string? endpoint_url = null;
private Soup.Session soup_session = null;
private bool transactions_stopped = false;
+ private Bytes? body = null;
+ private Error? transport_error= null;
+ private bool insecure = false;
public signal void wire_message_unqueued(Soup.Message message);
public signal void authenticated();
@@ -35,9 +38,8 @@ public abstract class Session {
this.endpoint_url = endpoint_url;
soup_session = new Soup.Session ();
if (Environment.get_variable("SHOTWELL_SOUP_LOG") != null) {
- soup_session.add_feature (new Soup.Logger (Soup.LoggerLogLevel.BODY, -1));
+ soup_session.add_feature (new Soup.Logger (Soup.LoggerLogLevel.BODY));
}
- this.soup_session.ssl_use_system_ca_file = true;
}
protected void notify_wire_message_unqueued(Soup.Message message) {
@@ -71,15 +73,33 @@ public abstract class Session {
if (are_transactions_stopped())
return;
- soup_session.request_unqueued.connect(notify_wire_message_unqueued);
- soup_session.send_message(message);
-
- soup_session.request_unqueued.disconnect(notify_wire_message_unqueued);
+ this.body = null;
+ this.transport_error = null;
+ try {
+ debug ("===================================== Send and read...");
+ this.body = soup_session.send_and_read(message, null);
+ debug ("====================================== This.body: %p", this.body);
+ } catch (Error error) {
+ debug ("Failed to send_and_read: %s", error.message);
+ this.transport_error = error;
+ }
+ notify_wire_message_unqueued(message);
}
public void set_insecure () {
- this.soup_session.ssl_use_system_ca_file = false;
- this.soup_session.ssl_strict = false;
+ this.insecure = true;
+ }
+
+ public bool get_is_insecure() {
+ return this.insecure;
+ }
+
+ public Error? get_transport_error() {
+ return this.transport_error;
+ }
+
+ public Bytes? get_body() {
+ return this.body;
}
}
@@ -163,7 +183,7 @@ public class Argument {
public string to_string (bool escape = false, bool encode = false) {
return "%s=%s%s%s".printf (this.key, escape ? "\"" : "",
- encode ? Soup.URI.encode(this.value, OAuth1.ENCODE_RFC_3986_EXTRA) : this.value,
+ encode ? GLib.Uri.escape_string(this.value) : this.value,
escape ? "\"" : "");
}
}
@@ -173,12 +193,13 @@ public class Transaction {
private bool is_executed = false;
private weak Session parent_session = null;
private Soup.Message message = null;
- private int bytes_written = 0;
+ private uint bytes_written = 0;
+ private ulong request_length;
private Spit.Publishing.PublishingError? err = null;
private string? endpoint_url = null;
private bool use_custom_payload;
- public signal void chunk_transmitted(int bytes_written_so_far, int total_bytes);
+ public signal void chunk_transmitted(uint bytes_written_so_far, uint total_bytes);
public signal void network_error(Spit.Publishing.PublishingError err);
public signal void completed();
@@ -201,12 +222,12 @@ public class Transaction {
message = new Soup.Message(method.to_string(), endpoint_url);
}
- private void on_wrote_body_data(Soup.Buffer written_data) {
- bytes_written += (int) written_data.length;
+ private void on_wrote_body_data(Soup.Message message, uint chunk_size) {
+ bytes_written += chunk_size;
while (Gtk.events_pending()) {
Gtk.main_iteration();
}
- chunk_transmitted(bytes_written, (int) message.request_body.length);
+ chunk_transmitted(bytes_written, (uint)request_length);
}
private void on_message_unqueued(Soup.Message message) {
@@ -225,7 +246,8 @@ public class Transaction {
/* Texts copied from epiphany */
public string detailed_error_from_tls_flags (out TlsCertificate cert) {
TlsCertificateFlags tls_errors;
- this.message.get_https_status (out cert, out tls_errors);
+ cert = this.message.get_tls_peer_certificate();
+ tls_errors = this.message.get_tls_peer_certificate_errors();
var list = new Gee.ArrayList<string> ();
if (TlsCertificateFlags.BAD_IDENTITY in tls_errors) {
@@ -276,38 +298,38 @@ public class Transaction {
}
protected void check_response(Soup.Message message) throws Spit.Publishing.PublishingError {
- switch (message.status_code) {
- case Soup.Status.OK:
- case Soup.Status.CREATED: // HTTP code 201 (CREATED) signals that a new
- // resource was created in response to a PUT or POST
- break;
-
- case Soup.Status.CANT_RESOLVE:
- case Soup.Status.CANT_RESOLVE_PROXY:
+ var transport_error = parent_session.get_transport_error();
+ if (transport_error != null) {
+ if (transport_error is GLib.ResolverError) {
throw new Spit.Publishing.PublishingError.NO_ANSWER("Unable to resolve %s (error code %u)",
- get_endpoint_url(), message.status_code);
-
- case Soup.Status.CANT_CONNECT:
- case Soup.Status.CANT_CONNECT_PROXY:
+ get_endpoint_url(), message.status_code);
+ }
+ if (transport_error is GLib.IOError) {
throw new Spit.Publishing.PublishingError.NO_ANSWER("Unable to connect to %s (error code
%u)",
get_endpoint_url(), message.status_code);
- case Soup.Status.SSL_FAILED:
+ }
+ if (transport_error is GLib.TlsError) {
throw new Spit.Publishing.PublishingError.SSL_FAILED ("Unable to connect to %s: Secure
connection failed",
get_endpoint_url ());
+ }
+
+ throw new Spit.Publishing.PublishingError.NO_ANSWER("Failure communicating with %s (error code
%u)",
+ get_endpoint_url(), message.status_code);
+}
+ switch (message.status_code) {
+ case Soup.Status.OK:
+ case Soup.Status.CREATED: // HTTP code 201 (CREATED) signals that a new
+ // resource was created in response to a PUT or POST
+ break;
default:
- // status codes below 100 are used by Soup, 100 and above are defined HTTP codes
- if (message.status_code >= 100) {
- throw new Spit.Publishing.PublishingError.NO_ANSWER("Service %s returned HTTP status
code %u %s",
- get_endpoint_url(), message.status_code, message.reason_phrase);
- } else {
- throw new Spit.Publishing.PublishingError.NO_ANSWER("Failure communicating with %s
(error code %u)",
- get_endpoint_url(), message.status_code);
- }
+ throw new Spit.Publishing.PublishingError.NO_ANSWER("Service %s returned HTTP status code %u
%s",
+ get_endpoint_url(), message.status_code, message.reason_phrase);
}
// All valid communication involves body data in the response
- if (message.response_body.data == null || message.response_body.data.length == 0)
+ var body = parent_session.get_body();
+ if (body == null || body.get_size() == 0)
throw new Spit.Publishing.PublishingError.MALFORMED_RESPONSE("No response data from %s",
get_endpoint_url());
}
@@ -324,13 +346,20 @@ public class Transaction {
this.is_executed = is_executed;
}
+ private bool on_accecpt_certificate(Soup.Message message, TlsCertificate cert, TlsCertificateFlags
errors) {
+ debug ("HTTPS connect error. Will ignore? %s", this.parent_session.get_is_insecure().to_string());
+ return this.parent_session.get_is_insecure();
+ }
+
protected void send() throws Spit.Publishing.PublishingError {
parent_session.wire_message_unqueued.connect(on_message_unqueued);
message.wrote_body_data.connect(on_wrote_body_data);
+ message.accept_certificate.connect(on_accecpt_certificate);
parent_session.send_wire_message(message);
parent_session.wire_message_unqueued.disconnect(on_message_unqueued);
message.wrote_body_data.disconnect(on_wrote_body_data);
+ message.accept_certificate.disconnect(on_accecpt_certificate);
if (err != null)
network_error(err);
@@ -367,7 +396,8 @@ public class Transaction {
}
ulong length = (payload_length > 0) ? payload_length : custom_payload.length;
- message.set_request(payload_content_type, Soup.MemoryUse.COPY, custom_payload.data[0:length]);
+ message.set_request_body_from_bytes(payload_content_type, new Bytes (custom_payload.data[0:length]));
+ this.request_length = length;
use_custom_payload = true;
}
@@ -377,8 +407,9 @@ public class Transaction {
// alone and let the Transaction class manage it for you. You should only need
// to install a new message if your subclass has radically different behavior from
// normal Transactions -- like multipart encoding.
- protected void set_message(Soup.Message message) {
+ protected void set_message(Soup.Message message, ulong request_length) {
this.message = message;
+ this.request_length = request_length;
}
public bool get_is_executed() {
@@ -406,42 +437,45 @@ public class Transaction {
assert(arguments.length > 0);
// concatenate the REST arguments array into an HTTP formdata string
- string formdata_string = "";
+ var formdata_string = new StringBuilder("");
for (int i = 0; i < arguments.length; i++) {
- formdata_string += arguments[i].to_string ();
+ formdata_string.append(arguments[i].to_string());
if (i < arguments.length - 1)
- formdata_string += "&";
+ formdata_string.append("&");
}
// for GET requests with arguments, append the formdata string to the endpoint url after a
// query divider ('?') -- but make sure to save the old (caller-specified) endpoint URL
// and restore it after the GET so that the underlying Soup message remains consistent
- string old_url = null;
+ GLib.Uri? old_url = null;
string url_with_query = null;
if (get_method() == HttpMethod.GET && arguments.length > 0) {
- old_url = message.get_uri().to_string(false);
- url_with_query = get_endpoint_url() + "?" + formdata_string;
- message.set_uri(new Soup.URI(url_with_query));
+ old_url = message.get_uri();
+ url_with_query = get_endpoint_url() + "?" + formdata_string.str;
+ try {
+ message.set_uri(GLib.Uri.parse(url_with_query, GLib.UriFlags.ENCODED));
+ } catch (Error err) {
+ error ("Invalid uri for service: %s", err.message);
+ }
} else {
- message.set_request("application/x-www-form-urlencoded", Soup.MemoryUse.COPY,
- formdata_string.data);
+ message.set_request_body_from_bytes("application/x-www-form-urlencoded",
StringBuilder.free_to_bytes((owned)formdata_string));
}
is_executed = true;
try {
- debug("sending message to URI = '%s'", message.get_uri().to_string(false));
+ debug("sending message to URI = '%s'", message.get_uri().to_string());
send();
} finally {
// if old_url is non-null, then restore it
if (old_url != null)
- message.set_uri(new Soup.URI(old_url));
+ message.set_uri(old_url);
}
}
public string get_response() {
assert(get_is_executed());
- return (string) message.response_body.data;
+ return parent_session.get_body() == null ? "" : (string) parent_session.get_body().get_data();
}
public unowned Soup.MessageHeaders get_response_headers() {
@@ -523,7 +557,7 @@ public class UploadTransaction : Transaction {
GLib.HashTable<string, string> result =
new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal);
- result.insert("filename", Soup.URI.encode(publishable.get_serialized_file().get_basename(),
+ result.insert("filename", GLib.Uri.escape_string(publishable.get_serialized_file().get_basename(),
null));
return result;
@@ -542,37 +576,33 @@ public class UploadTransaction : Transaction {
foreach (Argument arg in request_arguments)
message_parts.append_form_string(arg.key, arg.value);
- string payload;
- size_t payload_length;
+ MappedFile? mapped_file = null;
try {
- FileUtils.get_contents(publishable.get_serialized_file().get_path(), out payload,
- out payload_length);
- } catch (FileError e) {
+ mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
+ } catch (Error e) {
throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
_("A temporary file needed for publishing is unavailable"));
}
- int payload_part_num = message_parts.get_length();
-
- var bindable_data = new Soup.Buffer.take(payload.data[0:payload_length]);
message_parts.append_form_file("", publishable.get_serialized_file().get_path(), mime_type,
- bindable_data);
+ mapped_file.get_bytes());
unowned Soup.MessageHeaders image_part_header;
- unowned Soup.Buffer image_part_body;
+ unowned Bytes image_part_body;
+ int payload_part_num = message_parts.get_length() - 1;
message_parts.get_part(payload_part_num, out image_part_header, out image_part_body);
+ debug ("Image part header %p", image_part_header);
image_part_header.set_content_disposition("form-data", binary_disposition_table);
- Soup.Message outbound_message =
- Soup.Form.request_new_from_multipart(get_endpoint_url(), message_parts);
- // TODO: there must be a better way to iterate over a map
+ var outbound_message = new Soup.Message.from_multipart(get_endpoint_url(), message_parts);
+
Gee.MapIterator<string, string> i = message_headers.map_iterator();
bool cont = i.next();
while(cont) {
outbound_message.request_headers.append(i.get_key(), i.get_value());
cont = i.next();
}
- set_message(outbound_message);
+ set_message(outbound_message, mapped_file.get_length());
set_is_executed(true);
send();
@@ -742,7 +772,7 @@ public abstract class BatchUploader {
upload_complete(current_file);
}
- private void on_chunk_transmitted(int bytes_written_so_far, int total_bytes) {
+ private void on_chunk_transmitted(uint bytes_written_so_far, uint total_bytes) {
double file_span = 1.0 / publishables.length;
double this_file_fraction_complete = ((double) bytes_written_so_far) / total_bytes;
double fraction_complete = (current_file * file_span) + (this_file_fraction_complete *
diff --git a/plugins/common/WebAuthenticationPane.vala b/plugins/common/WebAuthenticationPane.vala
index f754b5d2..b9f7280b 100644
--- a/plugins/common/WebAuthenticationPane.vala
+++ b/plugins/common/WebAuthenticationPane.vala
@@ -39,7 +39,6 @@ namespace Shotwell.Plugins.Common {
box.pack_start (entry, false, false, 6);
this.webview = new WebKit.WebView ();
- this.webview.get_settings ().enable_plugins = false;
this.webview.load_changed.connect (this.on_page_load_changed);
this.webview.load_failed.connect (this.on_page_load_failed);
diff --git a/plugins/shotwell-publishing-extras/meson.build b/plugins/shotwell-publishing-extras/meson.build
index 3f7845a6..58073f71 100644
--- a/plugins/shotwell-publishing-extras/meson.build
+++ b/plugins/shotwell-publishing-extras/meson.build
@@ -1,8 +1,6 @@
shotwell_publishing_extra_sources = [
'GalleryConnector.vala',
- 'RajcePublishing.vala',
'shotwell-publishing-extras.vala',
- 'YandexPublishing.vala'
]
shotwell_publishing_extra_resources = gnome.compile_resources('publishing-extra-resource',
diff --git a/plugins/shotwell-publishing-extras/org.gnome.Shotwell.Publishing.Extras.gresource.xml
b/plugins/shotwell-publishing-extras/org.gnome.Shotwell.Publishing.Extras.gresource.xml
index 5916f823..1cf60634 100644
--- a/plugins/shotwell-publishing-extras/org.gnome.Shotwell.Publishing.Extras.gresource.xml
+++ b/plugins/shotwell-publishing-extras/org.gnome.Shotwell.Publishing.Extras.gresource.xml
@@ -2,11 +2,7 @@
<gresources>
<gresource prefix="/org/gnome/Shotwell/Publishing/Extras">
<file>gallery3.png</file>
- <file>rajce.png</file>
<file>gallery3_authentication_pane.ui</file>
<file>gallery3_publishing_options_pane.ui</file>
- <file>rajce_authentication_pane.ui</file>
- <file>rajce_publishing_options_pane.ui</file>
- <file>yandex_publish_model.ui</file>
</gresource>
</gresources>
diff --git a/plugins/shotwell-publishing-extras/shotwell-publishing-extras.vala
b/plugins/shotwell-publishing-extras/shotwell-publishing-extras.vala
index fa8c5e28..335556cf 100644
--- a/plugins/shotwell-publishing-extras/shotwell-publishing-extras.vala
+++ b/plugins/shotwell-publishing-extras/shotwell-publishing-extras.vala
@@ -10,13 +10,6 @@ private class ShotwellPublishingExtraServices : Object, Spit.Module {
private Spit.Pluggable[] pluggables = new Spit.Pluggable[0];
public ShotwellPublishingExtraServices(GLib.File module_file) {
-#if HAVE_YANDEX
- pluggables += new YandexService();
-#endif
-
-#if HAVE_RAJCE
- pluggables += new RajceService(module_file.get_parent());
-#endif
#if HAVE_GALLERY3
pluggables += new Gallery3Service(module_file.get_parent());
diff --git a/plugins/shotwell-publishing/FlickrPublishing.vala
b/plugins/shotwell-publishing/FlickrPublishing.vala
index bfa1c690..782cfacc 100644
--- a/plugins/shotwell-publishing/FlickrPublishing.vala
+++ b/plugins/shotwell-publishing/FlickrPublishing.vala
@@ -534,7 +534,7 @@ private class UploadTransaction : Publishing.RESTSupport.OAuth1.UploadTransactio
/// TODO: This may need to be revisited to send the title separately; please see
/// http://www.flickr.com/services/api/upload.api.html for more details.
- disposition_table.insert("filename", Soup.URI.encode(
+ disposition_table.insert("filename", GLib.Uri.escape_string(
publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME), null));
disposition_table.insert("name", "photo");
diff --git a/plugins/shotwell-publishing/PhotosUploader.vala b/plugins/shotwell-publishing/PhotosUploader.vala
index 5c73a10e..ed3b8830 100644
--- a/plugins/shotwell-publishing/PhotosUploader.vala
+++ b/plugins/shotwell-publishing/PhotosUploader.vala
@@ -11,7 +11,7 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
private PublishingParameters parameters;
private Publishing.RESTSupport.GoogleSession session;
private Spit.Publishing.Publishable publishable;
- private MappedFile mapped_file;
+ private InputStream mapped_file;
public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
@@ -30,11 +30,14 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
public override void execute() throws Spit.Publishing.PublishingError {
var basename = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ int64 mapped_file_size = -1;
// attempt to map the binary image data from disk into memory
try {
- mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
- } catch (FileError e) {
+ mapped_file = publishable.get_serialized_file().read(null);
+ var info = ((FileInputStream)mapped_file).query_info("standard::size", null);
+ mapped_file_size = info.get_size();
+ } catch (Error e) {
string msg = "Google Photos: couldn't read data from %s: %s".printf(
publishable.get_serialized_file().get_path(), e.message);
warning("%s", msg);
@@ -42,15 +45,6 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(msg);
}
- unowned uint8[] photo_data = (uint8[]) mapped_file.get_contents();
- photo_data.length = (int) mapped_file.get_length();
-
- // bind the binary image data read from disk into a Soup.Buffer object so that we
- // can attach it to the multipart request, then actaully append the buffer
- // to the multipart request. Then, set the MIME type for this part.
- // FIXME: Passing no free function here only works because we are sync
- Soup.Buffer bindable_data = new Soup.Buffer.with_owner(photo_data, mapped_file, null);
-
// create a message that can be sent over the wire whose payload is the multipart container
// that we've been building up
var outbound_message = new Soup.Message ("POST", get_endpoint_url());
@@ -59,8 +53,8 @@ internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.Authen
outbound_message.request_headers.append("X-Goog-Upload-File-Name", basename);
outbound_message.request_headers.append("X-Goog-Upload-Protocol", "raw");
outbound_message.request_headers.set_content_type("application/octet-stream", null);
- outbound_message.request_body.append_buffer (bindable_data);
- set_message(outbound_message);
+ outbound_message.set_request_body(null, mapped_file, (ssize_t)mapped_file_size);
+ set_message(outbound_message, (ulong)mapped_file_size);
// send the message and get its response
set_is_executed(true);
diff --git a/plugins/shotwell-publishing/PiwigoPublishing.vala
b/plugins/shotwell-publishing/PiwigoPublishing.vala
index f39032ab..acfb3f3f 100644
--- a/plugins/shotwell-publishing/PiwigoPublishing.vala
+++ b/plugins/shotwell-publishing/PiwigoPublishing.vala
@@ -339,9 +339,14 @@ public class PiwigoPublisher : Spit.Publishing.Publisher, GLib.Object {
private void do_show_ssl_downgrade_pane (SessionLoginTransaction trans,
string url) {
- var uri = new Soup.URI (url);
+ string host_name = "";
+ try {
+ host_name = GLib.Uri.parse (url, GLib.UriFlags.NONE).get_host();
+ } catch (Error err) {
+ debug("Failed to parse URL: %s", err.message);
+ }
host.set_service_locked (false);
- var ssl_pane = new SSLErrorPane (trans, uri.get_host ());
+ var ssl_pane = new SSLErrorPane (trans, host_name);
ssl_pane.proceed.connect (() => {
debug ("SSL: User wants us to retry with broken certificate");
this.session = new Session ();
@@ -1080,7 +1085,7 @@ internal class SSLErrorPane : Shotwell.Plugins.Common.BuilderPane {
base.constructed ();
var label = this.get_builder ().get_object ("main_text") as Gtk.Label;
- var bold_host = "<b>%s</b".printf(host);
+ var bold_host = "<b>%s</b>".printf(host);
// %s is the host name that we tried to connect to
label.set_text (_("This does not look like the real %s. Attackers might be trying to steal or alter
information going to or from this site (for example, private messages, credit card information, or
passwords).").printf(bold_host));
label.use_markup = true;
@@ -1847,7 +1852,7 @@ private class ImagesAddTransaction : Publishing.RESTSupport.UploadTransaction {
!basename.down().has_suffix(".jpg")) {
basename += ".jpg";
}
- disposition_table.insert("filename", Soup.URI.encode(basename, null));
+ disposition_table.insert("filename", GLib.Uri.escape_string(basename, null));
disposition_table.insert("name", "image");
set_binary_disposition_table(disposition_table);
@@ -1882,7 +1887,7 @@ private class ImagesAddRating : Publishing.RESTSupport.UploadTransaction {
try {
base.execute();
} catch (Spit.Publishing.PublishingError err) {
- debug("Rating upload error");
+ debug("Rating upload error. Ignored.");
}
}
}
diff --git a/plugins/shotwell-publishing/TumblrPublishing.vala
b/plugins/shotwell-publishing/TumblrPublishing.vala
index f80da0af..3525d227 100644
--- a/plugins/shotwell-publishing/TumblrPublishing.vala
+++ b/plugins/shotwell-publishing/TumblrPublishing.vala
@@ -612,7 +612,9 @@ namespace Publishing.Tumblr {
}
assert(form.size() > 0);
- var outbound_message = Soup.Form.request_new_from_hash ("POST", get_endpoint_url(), form);
+ var outbound_message = new Soup.Message ("POST", get_endpoint_url());
+ var body = new Bytes(Soup.Form.encode_hash(form).data);
+ outbound_message.set_request_body_from_bytes(Soup.FORM_MIME_TYPE_URLENCODED, body);
// TODO: there must be a better way to iterate over a map
Gee.MapIterator<string, string> i = base.message_headers.map_iterator();
@@ -621,7 +623,7 @@ namespace Publishing.Tumblr {
outbound_message.request_headers.append(i.get_key(), i.get_value());
cont = i.next();
}
- set_message(outbound_message);
+ set_message(outbound_message, body.length);
set_is_executed(true);
diff --git a/plugins/shotwell-publishing/YouTubePublishing.vala
b/plugins/shotwell-publishing/YouTubePublishing.vala
index 91927bd0..4b5908a4 100644
--- a/plugins/shotwell-publishing/YouTubePublishing.vala
+++ b/plugins/shotwell-publishing/YouTubePublishing.vala
@@ -60,10 +60,23 @@ private const string DEVELOPER_KEY =
private enum PrivacySetting {
PUBLIC,
UNLISTED,
- PRIVATE
+ PRIVATE;
+
+ public string to_string() {
+ switch (this) {
+ case PUBLIC:
+ return "public";
+ case UNLISTED:
+ return "unlisted";
+ case PRIVATE:
+ return "private";
+ default:
+ assert_not_reached();
+ }
+ }
}
-private class PublishingParameters {
+internal class PublishingParameters {
private PrivacySetting privacy;
private string? user_name;
@@ -89,44 +102,14 @@ private class PublishingParameters {
}
}
-internal class YouTubeAuthorizer : GData.Authorizer, Object {
- private RESTSupport.GoogleSession session;
- private Spit.Publishing.Authenticator authenticator;
-
- public YouTubeAuthorizer(RESTSupport.GoogleSession session, Spit.Publishing.Authenticator authenticator)
{
- this.session = session;
- this.authenticator = authenticator;
- }
-
- public bool is_authorized_for_domain(GData.AuthorizationDomain domain) {
- return domain.scope.has_suffix ("auth/youtube");
- }
-
- public void process_request(GData.AuthorizationDomain? domain,
- Soup.Message message) {
- if (domain == null) {
- return;
- }
-
- var header = "Bearer %s".printf(session.get_access_token());
- message.request_headers.replace("Authorization", header);
- }
-
- public bool refresh_authorization (GLib.Cancellable? cancellable = null) throws GLib.Error {
- this.authenticator.refresh();
- return true;
- }
-}
-
public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
private bool running;
private PublishingParameters publishing_parameters;
private Spit.Publishing.ProgressCallback? progress_reporter;
private Spit.Publishing.Authenticator authenticator;
- private GData.YouTubeService youtube_service;
public YouTubePublisher(Spit.Publishing.Service service, Spit.Publishing.PluginHost host) {
- base(service, host, "https://gdata.youtube.com/");
+ base(service, host, "https://www.googleapis.com/upload/youtube/v3/videos");
this.running = false;
this.publishing_parameters = new PublishingParameters();
@@ -161,8 +144,6 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
publishing_parameters.set_user_name(get_session().get_user_name());
- this.youtube_service = new GData.YouTubeService(DEVELOPER_KEY,
- new YouTubeAuthorizer(get_session(), this.authenticator));
do_show_publishing_options_pane();
}
@@ -261,7 +242,7 @@ public class YouTubePublisher : Publishing.RESTSupport.GooglePublisher {
return;
Spit.Publishing.Publishable[] publishables = get_host().get_publishables();
- Uploader uploader = new Uploader(this.youtube_service, get_session(), publishables,
publishing_parameters);
+ Uploader uploader = new Uploader(get_session(), publishables, publishing_parameters);
uploader.upload_complete.connect(on_upload_complete);
uploader.upload_error.connect(on_upload_error);
@@ -397,104 +378,20 @@ internal class PublishingOptionsPane : Spit.Publishing.DialogPane, GLib.Object {
}
}
-internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
- private const string ENDPOINT_URL = "https://uploads.gdata.youtube.com/feeds/api/users/default/uploads";
- private PublishingParameters parameters;
- private Publishing.RESTSupport.GoogleSession session;
- private Spit.Publishing.Publishable publishable;
- private GData.YouTubeService youtube_service;
-
- public UploadTransaction(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession
session,
- PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
- base(session, ENDPOINT_URL, Publishing.RESTSupport.HttpMethod.POST);
- assert(session.is_authenticated());
- this.session = session;
- this.parameters = parameters;
- this.publishable = publishable;
- this.youtube_service = youtube_service;
- }
-
- public override void execute() throws Spit.Publishing.PublishingError {
- var video = new GData.YouTubeVideo(null);
-
- var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- // Set title to publishing name, but if that's empty default to filename.
- string title = publishable.get_publishing_name();
- if (title == "") {
- title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
- }
- video.title = title;
-
- video.is_private = (parameters.get_privacy() == PrivacySetting.PRIVATE);
-
- if (parameters.get_privacy() == PrivacySetting.UNLISTED) {
- video.set_access_control("list", GData.YouTubePermission.DENIED);
- } else if (!video.is_private) {
- video.set_access_control("list", GData.YouTubePermission.ALLOWED);
- }
-
- var file = publishable.get_serialized_file();
-
- try {
- var info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE + "," +
- FileAttribute.STANDARD_SIZE, FileQueryInfoFlags.NONE);
- var upload_stream = this.youtube_service.upload_video(video, slug,
- info.get_content_type());
- var input_stream = file.read();
-
- // Yuck...
- var loop = new MainLoop(null, false);
- this.splice_with_progress.begin(info, input_stream, upload_stream, (obj, res) => {
- try {
- this.splice_with_progress.end(res);
- } catch (Error error) {
- critical("Failed to upload: %s", error.message);
- }
- loop.quit();
- });
- loop.run();
- video = this.youtube_service.finish_video_upload(upload_stream);
- } catch (Error error) {
- critical("Upload failed: %s", error.message);
- }
- }
-
- private async void splice_with_progress(GLib.FileInfo info, GLib.InputStream input, GLib.OutputStream
output) throws Error {
- var total_bytes = info.get_size();
- var bytes_to_write = total_bytes;
- uint8 buffer[8192];
-
- while (bytes_to_write > 0) {
- var bytes_read = yield input.read_async(buffer);
- if (bytes_read == 0)
- break;
-
- var bytes_written = yield output.write_async(buffer[0:bytes_read]);
- bytes_to_write -= bytes_written;
- chunk_transmitted((int)(total_bytes - bytes_to_write), (int) total_bytes);
- }
-
- yield output.close_async();
- yield input.close_async();
- }
-}
-
internal class Uploader : Publishing.RESTSupport.BatchUploader {
private PublishingParameters parameters;
- private GData.YouTubeService youtube_service;
- public Uploader(GData.YouTubeService youtube_service, Publishing.RESTSupport.GoogleSession session,
+ public Uploader(Publishing.RESTSupport.GoogleSession session,
Spit.Publishing.Publishable[] publishables, PublishingParameters parameters) {
base(session, publishables);
this.parameters = parameters;
- this.youtube_service = youtube_service;
}
protected override Publishing.RESTSupport.Transaction create_transaction(
Spit.Publishing.Publishable publishable) {
- return new UploadTransaction(this.youtube_service, (Publishing.RESTSupport.GoogleSession)
get_session(),
- parameters, get_current_publishable());
+ return new UploadTransaction((Publishing.RESTSupport.GoogleSession) get_session(),
+ parameters, get_current_publishable());
}
}
diff --git a/plugins/shotwell-publishing/YoutubeUploader.vala
b/plugins/shotwell-publishing/YoutubeUploader.vala
new file mode 100644
index 00000000..f98bf15c
--- /dev/null
+++ b/plugins/shotwell-publishing/YoutubeUploader.vala
@@ -0,0 +1,133 @@
+using Spit;
+
+namespace Publishing.YouTube {
+internal class UploadTransaction : Publishing.RESTSupport.GooglePublisher.AuthenticatedTransaction {
+ private PublishingParameters parameters;
+ private Publishing.RESTSupport.GoogleSession session;
+ private Spit.Publishing.Publishable publishable;
+ private MappedFile mapped_file;
+
+ public UploadTransaction(Publishing.RESTSupport.GoogleSession session,
+ PublishingParameters parameters, Spit.Publishing.Publishable publishable) {
+ base(session, "https://www.googleapis.com/upload/youtube/v3/videos",
+ Publishing.RESTSupport.HttpMethod.POST);
+ assert(session.is_authenticated());
+
+ this.session = session;
+ this.parameters = parameters;
+ this.publishable = publishable;
+ }
+
+ public Spit.Publishing.Publishable get_publishable() {
+ return this.publishable;
+ }
+
+ public override void execute() throws Spit.Publishing.PublishingError {
+ // Collect parameters
+
+ var slug = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ // Set title to publishing name, but if that's empty default to filename.
+ string title = publishable.get_publishing_name();
+ if (title == "") {
+ title = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ }
+
+ var builder = new Json.Builder();
+ builder.begin_object();
+ builder.set_member_name("snippet");
+ builder.begin_object();
+ builder.set_member_name("description");
+ builder.add_string_value(slug);
+ builder.set_member_name("title");
+ builder.add_string_value(title);
+ builder.end_object();
+ builder.set_member_name("status");
+ builder.begin_object();
+ builder.set_member_name("privacyStatus");
+ builder.add_string_value(parameters.get_privacy().to_string());
+ builder.end_object();
+ builder.end_object();
+
+ var meta_data = Json.to_string (builder.get_root(), false);
+ debug ("Parameters: %s", meta_data);
+ var message_parts = new Soup.Multipart("multipart/related");
+ var headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ var encoding = new GLib.HashTable<string, string>(str_hash, str_equal);
+ encoding.insert("encoding", "UTF-8");
+ headers.set_content_type ("application/json", encoding);
+
+ message_parts.append_part (headers, new Bytes (meta_data.data));
+ headers = new Soup.MessageHeaders(Soup.MessageHeadersType.MULTIPART);
+ headers.set_content_type ("application/octet-stream", null);
+ headers.append("Content-Transfer-Encoding", "binary");
+
+ MappedFile? mapped_file = null;
+ try {
+ mapped_file = new MappedFile(publishable.get_serialized_file().get_path(), false);
+ } catch (Error e) {
+ throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(
+ _("A temporary file needed for publishing is unavailable"));
+ }
+
+
+ message_parts.append_part (headers, mapped_file.get_bytes());
+
+ var outbound_message = new Soup.Message.from_multipart (get_endpoint_url() + "?part=" +
GLib.Uri.escape_string ("snippet,status"), message_parts);
+ outbound_message.get_request_headers().append("Authorization", "Bearer " +
+ session.get_access_token());
+
+ set_message(outbound_message, mapped_file.get_length() + meta_data.length);
+ set_is_executed(true);
+ send();
+
+#if 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var basename = publishable.get_param_string(Spit.Publishing.Publishable.PARAM_STRING_BASENAME);
+ int64 mapped_file_size = -1;
+
+ // attempt to map the binary image data from disk into memory
+ try {
+ mapped_file = publishable.get_serialized_file().read(null);
+ var info = ((FileInputStream)mapped_file).query_info("standard::size", null);
+ mapped_file_size = info.get_size();
+ } catch (Error e) {
+ string msg = "Google Photos: couldn't read data from %s: %s".printf(
+ publishable.get_serialized_file().get_path(), e.message);
+ warning("%s", msg);
+
+ throw new Spit.Publishing.PublishingError.LOCAL_FILE_ERROR(msg);
+ }
+
+ // create a message that can be sent over the wire whose payload is the multipart container
+ // that we've been building up
+ var outbound_message = new Soup.Message ("POST", get_endpoint_url());
+ outbound_message.request_headers.append("Authorization", "Bearer " +
+ session.get_access_token());
+ outbound_message.request_headers.append("X-Goog-Upload-File-Name", basename);
+ outbound_message.request_headers.append("X-Goog-Upload-Protocol", "raw");
+ outbound_message.request_headers.set_content_type("application/octet-stream", null);
+ outbound_message.set_request_body(null, mapped_file, (ssize_t)mapped_file_size);
+ set_message(outbound_message, (ulong)mapped_file_size);
+
+ // send the message and get its response
+ set_is_executed(true);
+ send();
+ #endif
+ }
+
+}
+}
diff --git a/plugins/shotwell-publishing/meson.build b/plugins/shotwell-publishing/meson.build
index 18c101aa..3096ee56 100644
--- a/plugins/shotwell-publishing/meson.build
+++ b/plugins/shotwell-publishing/meson.build
@@ -3,6 +3,7 @@ shotwell_publishing_sources = [
'FlickrPublishing.vala',
'TumblrPublishing.vala',
'YouTubePublishing.vala',
+ 'YoutubeUploader.vala',
'PiwigoPublishing.vala',
'PhotosPublisher.vala',
'PhotosService.vala',
@@ -17,7 +18,7 @@ shotwell_publishing_resources = gnome.compile_resources('publishing-resource',
shared_module('shotwell-publishing',
shotwell_publishing_sources + shotwell_publishing_resources,
dependencies : [gtk, soup, gexiv2, gee, sw_plugin, json_glib,
- webkit, sw_plugin_common_dep, xml, gdata, gcr,
+ webkit, sw_plugin_common_dep, xml, gcr,
gcr_ui, authenticator_dep, secret],
c_args : ['-DPLUGIN_RESOURCE_PATH="/org/gnome/Shotwell/Publishing"',
'-DGCR_API_SUBJECT_TO_CHANGE'],
diff --git a/test/server.py b/test/server.py
index 92ce04aa..72c7acfd 100755
--- a/test/server.py
+++ b/test/server.py
@@ -25,6 +25,49 @@ import http.server
import cgi
import urllib.parse
import time
+import argparse
+import ssl
+
+from OpenSSL import crypto, SSL
+
+def cert_gen(
+ emailAddress="emailAddress",
+ commonName="commonName",
+ countryName="NT",
+ localityName="localityName",
+ stateOrProvinceName="stateOrProvinceName",
+ organizationName="organizationName",
+ organizationUnitName="organizationUnitName",
+ serialNumber=0,
+ validityStartInSeconds=0,
+ validityEndInSeconds=10*365*24*60*60,
+ KEY_FILE = "key.pem",
+ CERT_FILE="cert.pem"):
+ #can look at generated file using openssl:
+ #openssl x509 -inform pem -in selfsigned.crt -noout -text
+ # create a key pair
+ k = crypto.PKey()
+ k.generate_key(crypto.TYPE_RSA, 4096)
+ # create a self-signed cert
+ cert = crypto.X509()
+ cert.get_subject().C = countryName
+ cert.get_subject().ST = stateOrProvinceName
+ cert.get_subject().L = localityName
+ cert.get_subject().O = organizationName
+ cert.get_subject().OU = organizationUnitName
+ cert.get_subject().CN = commonName
+ cert.get_subject().emailAddress = emailAddress
+ cert.set_serial_number(serialNumber)
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(validityEndInSeconds)
+ cert.set_issuer(cert.get_subject())
+ cert.set_pubkey(k)
+ cert.sign(k, 'sha512')
+ with open(CERT_FILE, "wt") as f:
+ f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8"))
+ with open(KEY_FILE, "wt") as f:
+ f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode("utf-8"))
+
# This is a simple implementation of the Piwigo protocol to run locally
# for testing publishing in offline-situations
@@ -61,7 +104,6 @@ class SimpleRequestHandler(http.server.BaseHTTPRequestHandler):
if self.path == '/ws.php':
try:
-
if method == 'pwg.session.login':
self.send_response(200)
self.send_header('Content-type', 'text/xml')
@@ -95,15 +137,33 @@ class SimpleRequestHandler(http.server.BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(b'<?xml version="1.0"?><piwigo
stat="ok"><image_id>2387</image_id></piwigo>')
return
+ elif method == 'pwg.images.rate':
+ self.send_response(200)
+ self.send_header('Set-Cookie', 'pwg_id="12345"')
+ self.end_headers()
+ self.wfile.write(b'<?xml version="1.0"?><piwigo
stat="ok"><image_id>2387</image_id></piwigo>')
+ return
except:
self.log_error('Unknown method {0}'.format(postvars[b'method']))
pass
self.send_response(500)
-def run(server_class = http.server.HTTPServer, handler_class = SimpleRequestHandler):
- server_address = ('127.0.0.1', 8080)
+def run(server_class = http.server.HTTPServer, handler_class = SimpleRequestHandler, port=8080,
do_ssl=False):
+ server_address = ('127.0.0.1', port)
httpd = server_class(server_address, handler_class)
+ if do_ssl:
+ cert_gen()
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain("cert.pem", "key.pem")
+ httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
+
httpd.serve_forever()
-run()
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description = "Piwigo test server")
+ parser.add_argument('--port', type=int, default=8080)
+ parser.add_argument('--ssl', action='store_true')
+ args = parser.parse_args()
+
+ run(port=args.port, do_ssl = args.ssl)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]